summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/mozilla/tests
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/mozilla/tests')
-rw-r--r--testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html30
-rw-r--r--testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html22
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.http.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.https.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.http.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.https.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.http.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.https.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.http.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.https.html118
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.http.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.https.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.http.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.https.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.http.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.https.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.http.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.https.html119
-rw-r--r--testing/web-platform/mozilla/tests/content-security-policy/generic/test-case.sub.js98
-rw-r--r--testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html44
-rw-r--r--testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html120
-rw-r--r--testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html139
-rw-r--r--testing/web-platform/mozilla/tests/css/css-grid/alignment/grid-item-aspect-ratio-justify-self-003.html146
-rw-r--r--testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-005.html74
-rw-r--r--testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-006.html74
-rw-r--r--testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-007.html86
-rw-r--r--testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-008.html80
-rw-r--r--testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-009.html78
-rw-r--r--testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-events.html56
-rw-r--r--testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html115
-rw-r--r--testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos-ref.html21
-rw-r--r--testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos.html23
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html2
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html8
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/moz-transform-exists.html22
-rw-r--r--testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html35
-rw-r--r--testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html2
-rw-r--r--testing/web-platform/mozilla/tests/css/file-selector-button-margin.html10
-rw-r--r--testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print-ref.html5
-rw-r--r--testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print.sub.html6
-rw-r--r--testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html40
-rw-r--r--testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html14
-rw-r--r--testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht19
-rw-r--r--testing/web-platform/mozilla/tests/css/resources/iframe-os-text-scale-inner.html9
-rw-r--r--testing/web-platform/mozilla/tests/dom/classList.html526
-rw-r--r--testing/web-platform/mozilla/tests/dom/delayed_window_print.html39
-rw-r--r--testing/web-platform/mozilla/tests/dom/dispatch_select_event.html35
-rw-r--r--testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html63
-rw-r--r--testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js70
-rw-r--r--testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html45
-rw-r--r--testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html20
-rw-r--r--testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js15
-rw-r--r--testing/web-platform/mozilla/tests/dom/range-in-two-selections.html34
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/resources/test.html5
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js136
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js3
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js10
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js11
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js11
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js11
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js35
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js35
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js35
-rw-r--r--testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js37
-rw-r--r--testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html278
-rw-r--r--testing/web-platform/mozilla/tests/editor/input-setRangeText-during-noframe-crash.html18
-rw-r--r--testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html371
-rw-r--r--testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js51
-rw-r--r--testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority-disabled.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.js107
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests-data.js40
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests/fetch-init.h2.html66
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests-data.js21
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/font-face-worker.js5
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-head.h2.html24
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-script.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-worker.h2.html19
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests-data.js96
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-dynamic-load.h2.html41
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-initial-load.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-dynamic-load.h2.html50
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-initial-load.h2.html30
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/shape-outside-image.h2.html29
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests-data.js471
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-load-stylesheet.h2.html74
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-modulepreload.h2.html36
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-prefetch.h2.html36
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-fetch.h2.html37
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-font.h2.html37
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-image.h2.html37
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-script.h2.html37
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-style.h2.html37
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-header.h2.html14
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-load-stylesheet.h2.html26
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-modulepreload.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-prefetch.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-fetch.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-font.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-image.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-script.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-style.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.css1
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.font0
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.image0
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.js0
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.txt0
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/square_25px_x_25px.pngbin0 -> 570 bytes
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests-data.js216
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-module-script-initial-load.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-script-initial-load.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/deferred-script-initial-load.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-dynamic-insertion.h2.html37
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-initial-load.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-dynamic-insertion.h2.html35
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-body.h2.html27
-rw-r--r--testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-head.h2.html18
-rw-r--r--testing/web-platform/mozilla/tests/fetch/orb/tentative/content-range.sub.window.js19
-rw-r--r--testing/web-platform/mozilla/tests/fetch/orb/tentative/img-mime-types-coverage.tentative.sub.html43
-rw-r--r--testing/web-platform/mozilla/tests/fetch/orb/tentative/known-mime-type.sub.window.js48
-rw-r--r--testing/web-platform/mozilla/tests/fetch/orb/tentative/nosniff.sub.window.js43
-rw-r--r--testing/web-platform/mozilla/tests/fetch/orb/tentative/resources/utils.js28
-rw-r--r--testing/web-platform/mozilla/tests/fetch/orb/tentative/status.sub.window.js30
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_collapse.html207
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_selectNode.html267
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_setEnd.html364
-rw-r--r--testing/web-platform/mozilla/tests/focus/Range_setStart.html353
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange.html1242
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html63
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html67
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html9
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_collapse.html148
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html134
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html142
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_extend.html189
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html112
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_removeRange.html112
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html254
-rw-r--r--testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html926
-rw-r--r--testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html19
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html16
-rw-r--r--testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html75
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html42
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html42
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html50
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html17
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html50
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html42
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html31
-rw-r--r--testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html42
-rw-r--r--testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/navigating-across-documents/location-hash.sub.html66
-rw-r--r--testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html24
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html59
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html41
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html46
-rw-r--r--testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-04.html48
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js95
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html61
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html217
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/input-activation-behavior.html103
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html13
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html14
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html7
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html15
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html7
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html14
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html48
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html28
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html21
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html26
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html25
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/inline-module-order.html40
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html27
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html89
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html41
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js11
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js5
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js5
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js14
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js4
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js12
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js5
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js8
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js6
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/empty_module.js0
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs19
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js1
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js2
-rw-r--r--testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js3
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/README.md7
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html10
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html9
-rw-r--r--testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/LICENSE359
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/README.txt14
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/elango.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/htchanakya.html12
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/shreetam.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tab.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tam.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tboomi.html10
-rw-r--r--testing/web-platform/mozilla/tests/indic-detection/tscii.html10
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html12
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html11
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html10
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html5
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html5
-rw-r--r--testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html7
-rw-r--r--testing/web-platform/mozilla/tests/mathml/README.md57
-rw-r--r--testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.xhtml133
-rw-r--r--testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.html130
-rw-r--r--testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.xhtml134
-rw-r--r--testing/web-platform/mozilla/tests/mathml/disabled/dynamic-math-tree-001.html47
-rw-r--r--testing/web-platform/mozilla/tests/mathml/disabled/math-parse01.html62
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/README6
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/axis-height-1.otfbin0 -> 1968 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/axis-height-2.otfbin0 -> 1968 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/default-font-ref.html14
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/default-font.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-1-ref.html132
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.html135
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.otfbin0 -> 1812 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-2-ref.html175
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-2.html214
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-3-ref.html175
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/dtls-3.html217
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1-ref.html74
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1.html108
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/generate.py320
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/limits-5.otfbin0 -> 1872 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1-ref.html45
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1.html38
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2-ref.html25
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2.html40
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/mathssty.woffbin0 -> 1384 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/opentype-axis-height.html51
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/opentype-limits.html60
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy-ref.html69
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy.html70
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/ssty-1-ref.html337
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/ssty-1.html325
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/ssty-2-ref.html275
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/ssty-2.html268
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/ssty.woffbin0 -> 1412 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/fonts/stretchy.otfbin0 -> 3336 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathml-console-messages.html245
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathml-type-supported-ref.xml4
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathml-type-supported.xhtml12
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace-ref.html54
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace.html57
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace-ref.html54
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace.html57
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a-ref.html233
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a.html225
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b-ref.html126
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b.html127
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c-ref.html247
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c.html248
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d-ref.html64
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d.html65
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2-ref.html25
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2.html26
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4-ref.html55
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4.html47
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5-ref.html49
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5.html71
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font.html20
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur-ref.html79
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur.html85
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic-ref.html137
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic.html143
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-ref.html149
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif-ref.html147
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif.html153
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script-ref.html79
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script.html85
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold.html155
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity-ref.html163
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity.html168
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight-ref.html25
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight.html30
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-ref.html114
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck.html120
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur-ref.html79
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur.html85
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial-ref.html47
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial.html53
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic-ref.html139
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic.html145
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped-ref.html54
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped.html60
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace-ref.html89
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace.html95
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic-ref.html137
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic.html143
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic-ref.html79
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic.html85
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-ref.html89
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif.html95
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script-ref.html79
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script.html85
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched-ref.html50
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched.html56
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed-ref.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed.html48
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1-ref.html21
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1a.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1b.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1d.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1e.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1f.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1g.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1h.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1i.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1j.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1k.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1l.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1m.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1n.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1o.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1p.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-1q.html14
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial-ref.html44
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box-ref.html45
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box.html44
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle-ref.html44
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv-ref.html45
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv.html44
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle-ref.html47
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle.html46
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox-ref.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox.html41
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow-ref.html44
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike-ref.html43
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid-ref.html13
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-4.html30
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-actuarial.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-bottom.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-box.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-circle.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-downdiagonalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-horizontalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-left.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-longdiv.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-madruwb.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-phasorangle.html23
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-right.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-roundedbox.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-top.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalarrow.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-verticalstrike.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir-ref.html61
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir.html64
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/menclose-in-mphantom-ref.html12
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation.html17
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic-ref.html12
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic.html20
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10.html12
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11-ref.html11
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11.html12
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001-ref.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001-ref.html45
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001.html21
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002-ref.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002.html19
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001-ref.xhtml9
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001.xhtml16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1-ref.html25
-rw-r--r--testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1.html30
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-1-ref.html77
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-1a.html78
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-1b.html78
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-1c.html78
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-2-ref.html41
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-2a.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-2b.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1-ref.html32
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1.html49
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2-ref.html32
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2.html49
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/dir-6a-ref.html67
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/dir-6a.html70
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mstyle-align-ref.html26
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mstyle-align.html27
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2-ref.html21
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2.html22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-ref.html15
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber.html16
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace-ref.html342
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace.html339
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable-dynamic.html147
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr-dynamic.html156
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-ref.html128
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable-dynamic.html147
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr-dynamic.html156
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-ref.html128
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-mtable-dynamic.html106
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-ref.html90
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-mtable-dynamic.html106
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-ref.html90
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable-dynamic.html147
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-ref.html128
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable-dynamic.html147
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr-dynamic.html156
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr.html129
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-ref.html128
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-mtable-dynamic.html106
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-ref.html90
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-mtable-dynamic.html106
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-ref.html90
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-width-ref.html44
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/mtable-width.html46
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-1-ref.html77
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-1a.html78
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-1b.html78
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-1c.html78
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-2-ref.html51
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-2a.html52
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-2b.html52
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1-ref.html40
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1.html60
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2-ref.html40
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2.html60
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496-ref.xhtml22
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496.xhtml25
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/semantics-4-ref.html24
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/semantics-4.html41
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/spacing-attributes-001.html103
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1-ref.html66
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1.html69
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2-ref.html134
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2.html137
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3-ref.html133
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3.html136
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4-ref.html95
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4.html124
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5-ref.html274
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5.html296
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a-ref.html278
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a.html296
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6-ref.html123
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6.html136
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7-ref.html97
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7.html100
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8-ref.html38
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8a.html42
-rw-r--r--testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8b.html41
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3-ref.html32
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3.html32
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1-ref.html2
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1.html3
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2-ref.html2
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2.html3
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3-ref.html2
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3.html3
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4-ref.html2
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4.html3
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5-ref.html5
-rw-r--r--testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5.html6
-rw-r--r--testing/web-platform/mozilla/tests/media/2x2-green.ogvbin0 -> 7660 bytes
-rw-r--r--testing/web-platform/mozilla/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html110
-rw-r--r--testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html67
-rw-r--r--testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html58
-rw-r--r--testing/web-platform/mozilla/tests/mediacapture-streams/permission-helper.js24
-rw-r--r--testing/web-platform/mozilla/tests/placeholder6
-rw-r--r--testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html30
-rw-r--r--testing/web-platform/mozilla/tests/selection/Selection-addRange-same-instance.html56
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html34
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/blank.html2
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html15
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js26
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html24
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html22
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/empty.js0
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py14
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/green.pngbin0 -> 87 bytes
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js14
-rw-r--r--testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html63
-rw-r--r--testing/web-platform/mozilla/tests/svg/smil-sampling.html44
-rw-r--r--testing/web-platform/mozilla/tests/web-animations/web-animations-no-infinite-refresh.html31
-rw-r--r--testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html4
-rw-r--r--testing/web-platform/mozilla/tests/web-animations/web-animations-print.html20
-rw-r--r--testing/web-platform/mozilla/tests/webaudio/the-audio-api/the-audioparam-interface/large-exponentialRamp.html47
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browser/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/close.py36
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py20
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py72
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py31
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py48
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/invalid.py19
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/viewport.py18
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/conftest.py93
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py8
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py26
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/script/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py61
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/end/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/end/end.py53
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/end/invalid.py15
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/always_match.py18
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/capabilities.py51
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/first_match.py18
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/invalid.py66
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/response.py84
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/test_data.py143
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/storage/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/partition.py131
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/partition.py150
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py158
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py45
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py30
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/scroll_into_view.py50
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/send_keys.py44
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/execute_async.py71
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/chrome.py25
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/chrome.py43
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/bidi_disabled.py33
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/binary.py33
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/conftest.py58
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/create.py11
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/invalid.py53
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/new_session/profile_root.py43
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/perform_actions/wheel.py37
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/protocol/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_hosts.py53
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_origins.py56
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/protocol/marionette_port.py41
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/protocol/request.py72
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/send_alert_text.py22
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/__init__.py12
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/iframe.py47
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/screenshot.py51
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/conftest.py15
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/harness/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/harness/crash_content_process.py6
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/harness/crash_parent_process.py6
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/harness/preferences.py6
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/__init__.py0
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/context.py20
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/fixtures.py418
-rw-r--r--testing/web-platform/mozilla/tests/webdriver/support/network.py57
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/data_cache.js175
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js358
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/metadata.js28
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/params_builder.js389
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/resources.js110
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js32
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/framework/test_group.js3
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js105
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js30
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js4
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js184
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/params_utils.js138
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js95
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/encode_selectively.js23
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/json_param_value.js114
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js155
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js262
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/separators.js14
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/stringify_params.js44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/query/validQueryPart.js3
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/stack.js82
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js743
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js6
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js671
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/util.js10
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/version.js3
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js52
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js129
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js49
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/collect_garbage.js58
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/colors.js127
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/data_tables.js129
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/navigator_gpu.js86
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/preprocessor.js149
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/timeout.js7
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/types.js97
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/util.js476
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/util/wpt_reftest_wait.js24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapterInfo/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestDevice/cts.https.html44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_ArrayBuffer/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_detach/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_oom/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/threading/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/basic/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/clearBuffer/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyBufferToBuffer/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyTextureToTexture/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/image_copy/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/programmable/state_tracking/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/queries/occlusionQuery/cts.https.html44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/render/state_tracking/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute/basic/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute_pipeline/overrides/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/device/lost/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/labels/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/multiple_buffers/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/single_buffer/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/texture/same_subresource/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/onSubmittedWorkDone/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/pipeline/default_layout/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/queue/writeBuffer/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/reflection/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/clear_value/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/resolve/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeOp/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeop2/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/culling_tests/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/overrides/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/pipeline_output_targets/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/primitive_topology/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/sample_mask/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/basic/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/color_target_state/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_bias/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_clip_clamp/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/draw/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/indirect_draw/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/stencil/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/buffer/cts.https.html51
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/texture_zero/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/anisotropy/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/filter_mode/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/shader_module/compilation_info/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/format_reinterpretation/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/read/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/write/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/uncapturederror/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/correctness/cts.https.html45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/index_format/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/create/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/destroy/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/mapping/cts.https.html68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/query_types/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/texture_formats/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindGroups/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBufferSize/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachments/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexAttributes/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBuffers/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/compute_pipeline/cts.https.html51
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroup/cts.https.html57
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroupLayout/cts.https.html46
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createPipelineLayout/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createSampler/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createTexture/cts.https.html52
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createView/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/debugMarker/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginComputePass/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginRenderPass/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/clearBuffer/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/compute_pass/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyBufferToBuffer/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyTextureToTexture/cts.https.html47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/debug/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/index_access/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/draw/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/dynamic_state/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/indirect_draw/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setIndexBuffer/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setPipeline/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setVertexBuffer/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/state_tracking/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/setBindGroup/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/createRenderBundleEncoder/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_open_state/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_state/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/begin_end/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/general/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/resolveQuerySet/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/render_bundle/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/error_scope/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/getBindGroupLayout/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/gpu_external_texture_expiration/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_related/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_texture_copies/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/layout_related/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/texture_related/cts.https.html44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/create/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/destroy/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/buffer_mapped/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture/cts.https.html47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/buffer/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/query_set/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/texture/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/submit/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeBuffer/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeTexture/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/attachment_compatibility/cts.https.html47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/render_pass_descriptor/cts.https.html62
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/resolve/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/depth_stencil_state/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/fragment_state/cts.https.html45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/inter_stage/cts.https.html44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/misc/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/multisample_state/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/overrides/cts.https.html45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/primitive_state/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/shader_module/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/vertex_state/cts.https.html47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_encoder/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_misc/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_pass_encoder/cts.https.html46
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_common/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_misc/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/entry_point/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/overrides/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/state/device_lost/destroy/cts.https.html67
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/bgra8unorm_storage/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/destroy/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/float32_filterable/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/rg11b10ufloat_renderable/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/fragment_state/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/shader_module/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/vertex_state/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/createTexture/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/cubeArray/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/examples/cts.https.html50
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/idl/constants/flags/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_addition/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_comparison/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_division/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_addition/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_subtraction/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_multiplication/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_remainder/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_subtraction/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise_shift/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bool_logical/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_addition/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_comparison/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_division/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_addition/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_subtraction/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_multiplication/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_remainder/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_subtraction/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_addition/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_comparison/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_division/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_addition/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_subtraction/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_multiplication/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_remainder/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_subtraction/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_arithmetic/cts.https.html60
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_comparison/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_arithmetic/cts.https.html60
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_comparison/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/abs/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acos/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acosh/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/all/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/any/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/arrayLength/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asin/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asinh/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan2/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atanh/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/bitcast/cts.https.html57
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ceil/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/clamp/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cos/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cosh/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countLeadingZeros/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countOneBits/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countTrailingZeros/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cross/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/degrees/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/determinant/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/distance/cts.https.html44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dot/cts.https.html45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdx/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxCoarse/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxFine/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdy/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyCoarse/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyFine/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp2/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/extractBits/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/faceForward/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstLeadingBit/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstTrailingBit/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/floor/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fma/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fract/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/frexp/cts.https.html51
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidth/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthCoarse/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthFine/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/insertBits/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/inversesqrt/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ldexp/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/length/cts.https.html44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log2/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/max/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/min/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/mix/cts.https.html47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/modf/cts.https.html59
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/normalize/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16float/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16snorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16unorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8snorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8unorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pow/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/quantizeToF16/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/radians/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reflect/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/refract/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reverseBits/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/round/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/saturate/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/select/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sign/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sin/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sinh/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/smoothstep/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sqrt/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/step/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/storageBarrier/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tan/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tanh/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureDimension/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGather/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGatherCompare/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureLoad/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLayers/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLevels/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumSamples/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSample/cts.https.html46
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleBias/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompare/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleGrad/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleLevel/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureStore/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/transpose/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/trunc/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16float/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/workgroupBarrier/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_arithmetic/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_assignment/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_conversion/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_logical/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_arithmetic/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_conversion/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_arithmetic/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_conversion/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_arithmetic/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_complement/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_conversion/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_complement/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_conversion/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/float_parse/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/call/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/complex/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/eval_order/cts.https.html71
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/for/cts.https.html46
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/if/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/loop/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/phony/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/return/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/switch/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/while/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/adjacent/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/atomicity/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/barrier/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/coherence/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/weak/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/padding/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access_vertex/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/compute_builtins/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/shared_structs/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shadow/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/statement/increment_decrement/cts.https.html50
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/zero_init/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/const_assert/const_assert/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/const/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/override/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/ptr_spelling/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/var_access_mode/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/access/vector/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/binary/bitwise_shift/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/abs/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acos/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acosh/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asin/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asinh/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan2/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atanh/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atomics/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/bitcast/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/ceil/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/clamp/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cos/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cosh/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/degrees/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp2/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/inverseSqrt/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/length/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log2/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/modf/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/radians/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/round/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/saturate/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sign/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sin/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sinh/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sqrt/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/tan/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/alias_analysis/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/restrictions/cts.https.html46
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/align/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/attribute/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/binary_ops/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/blankspace/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/break/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/builtin/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/comments/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const_assert/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/diagnostic/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/discard/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/enable/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/identifiers/cts.https.html46
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/literal/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/must_use/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/pipeline_stage/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/semicolon/cts.https.html74
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/source/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/unary_ops/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/var_and_let/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/binding/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/builtins/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/entry_point/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group_and_binding/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/id/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/interpolate/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/invariant/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/locations/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/size/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/workgroup_size/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/alias/cts.https.html45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/struct/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/vector/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/uniformity/uniformity/cts.https.html45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texel_data/cts.https.html41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texture_ok/cts.https.html38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/configure/cts.https.html43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/context_creation/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getCurrentTexture/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getPreferredCanvasFormat/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/readbackFromWebGPUCanvas/cts.https.html42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageData/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/canvas/cts.https.html40
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/image/cts.https.html37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/video/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/external_texture/video/cts.https.html39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/worker/worker/cts.https.html36
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.d.js470
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.js1228
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/README.md15
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-180.mp4bin0 -> 16261 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-270.mp4bin0 -> 16261 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-90.mp4bin0 -> 16261 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601.mp4bin0 -> 16261 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-theora-bt601.ogvbin0 -> 44488 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp8-bt601.webmbin0 -> 17910 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt601.webmbin0 -> 13116 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt709.webmbin0 -> 12584 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/four-colors.pngbin0 -> 840 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/resources/webgpu.pngbin0 -> 33475 bytes
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapter.spec.js124
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapterInfo.spec.js54
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestDevice.spec.js376
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map.spec.js510
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_ArrayBuffer.spec.js89
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_detach.spec.js79
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_oom.spec.js50
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/mapping_test.js39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/threading.spec.js29
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/basic.spec.js98
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/clearBuffer.spec.js54
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyBufferToBuffer.spec.js108
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.js1686
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/image_copy.spec.js2098
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/programmable_state_test.js157
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/state_tracking.spec.js306
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.js1033
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/dynamic_state.spec.js19
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/state_tracking.spec.js624
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute/basic.spec.js162
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/entry_point_name.spec.js12
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/overrides.spec.js503
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/device/lost.spec.js92
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/labels.spec.js280
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.js942
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/multiple_buffers.spec.js354
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/single_buffer.spec.js257
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/operation_context_helper.js330
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/same_subresource.spec.js709
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/texture_sync_test.js124
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/onSubmittedWorkDone.spec.js56
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/pipeline/default_layout.spec.js27
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/queue/writeBuffer.spec.js235
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/reflection.spec.js137
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/clear_value.spec.js188
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/resolve.spec.js183
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js354
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeop2.spec.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js359
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/overrides.spec.js453
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.js450
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/primitive_topology.spec.js488
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/sample_mask.spec.js806
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.js29
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/basic.spec.js353
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/color_target_state.spec.js818
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth.spec.js546
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_bias.spec.js352
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_clip_clamp.spec.js524
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/draw.spec.js768
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/indirect_draw.spec.js242
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/robust_access_index.spec.js8
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/stencil.spec.js584
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/buffer.spec.js899
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_copy.js66
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_ds_test.js200
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_sampling.js157
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/texture_zero.spec.js645
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/anisotropy.spec.js325
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/filter_mode.spec.js1143
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/lod_clamp.spec.js12
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/shader_module/compilation_info.spec.js264
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/format_reinterpretation.spec.js358
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/read.spec.js56
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/write.spec.js54
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/uncapturederror.spec.js34
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/correctness.spec.js1180
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/index_format.spec.js584
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/create.spec.js113
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/destroy.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/mapping.spec.js1125
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/threading.spec.js14
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/query_types.spec.js76
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/texture_formats.spec.js463
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/limit_utils.js1089
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.js95
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup.spec.js75
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBufferSize.spec.js28
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample.spec.js260
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachments.spec.js124
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup.spec.js147
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX.spec.js20
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY.spec.js20
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ.spec.js20
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.js182
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension.spec.js97
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout.spec.js39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout.spec.js42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.js151
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables.spec.js44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.js144
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.js145
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize.spec.js161
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.js174
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.js156
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers.spec.js27
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D.spec.js34
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D.spec.js133
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D.spec.js39
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize.spec.js90
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.js144
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexAttributes.spec.js43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride.spec.js121
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.js100
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment.spec.js183
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment.spec.js186
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/compute_pipeline.spec.js692
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroup.spec.js1110
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js464
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createPipelineLayout.spec.js164
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createSampler.spec.js68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createTexture.spec.js1130
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createView.spec.js340
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/debugMarker.spec.js98
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginComputePass.spec.js147
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginRenderPass.spec.js215
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/clearBuffer.spec.js246
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/compute_pass.spec.js259
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyBufferToBuffer.spec.js326
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.js874
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/debug.spec.js66
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js162
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/draw.spec.js877
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.js319
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/indirect_draw.spec.js202
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/render.js29
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setIndexBuffer.spec.js124
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setPipeline.spec.js62
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setVertexBuffer.spec.js144
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.js184
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render_pass.spec.js14
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/setBindGroup.spec.js435
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.js259
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_open_state.spec.js587
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_state.spec.js250
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js777
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/begin_end.spec.js117
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/common.js37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/general.spec.js152
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/resolveQuerySet.spec.js181
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/render_bundle.spec.js258
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/error_scope.spec.js291
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/getBindGroupLayout.spec.js201
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/gpu_external_texture_expiration.spec.js332
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_related.spec.js226
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_texture_copies.spec.js453
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/image_copy.js278
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/layout_related.spec.js483
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/texture_related.spec.js534
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/layout_shader_compat.spec.js14
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/create.spec.js34
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/destroy.spec.js33
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/buffer_mapped.spec.js280
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js816
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/buffer.spec.js296
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/query_set.spec.js63
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/texture.spec.js294
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/submit.spec.js47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeBuffer.spec.js200
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeTexture.spec.js110
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/attachment_compatibility.spec.js690
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/render_pass_descriptor.spec.js1097
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/resolve.spec.js192
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/common.js68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.js304
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/fragment_state.spec.js427
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/inter_stage.spec.js324
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/misc.spec.js98
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/multisample_state.spec.js87
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/overrides.spec.js535
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/primitive_state.spec.js42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/shader_module.spec.js112
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/vertex_state.spec.js765
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.js928
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.js409
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.js1395
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_common.spec.js566
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.js420
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/entry_point.spec.js117
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/overrides.spec.js96
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/state/device_lost/destroy.spec.js1170
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/bgra8unorm_storage.spec.js205
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/destroy.spec.js139
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/float32_filterable.spec.js58
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/rg11b10ufloat_renderable.spec.js149
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/validation_test.js448
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/capability_info.js792
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.js44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js423
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/fragment_state.spec.js128
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/shader_module.spec.js74
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.js91
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/createTexture.spec.js41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/cubeArray.spec.js26
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/compat/compatibility_test.js10
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/constants.js62
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/examples.spec.js275
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/format_info.js1273
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js1681
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/idl/constants/flags.spec.js79
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.html.js52
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.http.html13
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.https.html13
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/idl/idl_test.js41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/listing.js4223
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_addition.spec.js154
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_comparison.spec.js214
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_division.spec.js154
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.js61
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.js61
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_multiplication.spec.js154
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_remainder.spec.js154
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_subtraction.spec.js154
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/binary.js21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise.spec.js303
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise_shift.spec.js343
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bool_logical.spec.js187
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_addition.spec.js212
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_comparison.spec.js280
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_division.spec.js212
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.js114
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.js161
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.js156
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_multiplication.spec.js212
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_remainder.spec.js212
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_subtraction.spec.js212
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_addition.spec.js194
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_comparison.spec.js262
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_division.spec.js194
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.js95
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.js108
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.js152
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.js95
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.js147
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_multiplication.spec.js194
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_remainder.spec.js194
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_subtraction.spec.js194
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.js738
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_comparison.spec.js121
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.js725
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_comparison.spec.js121
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/abs.spec.js196
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acos.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acosh.spec.js81
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/all.spec.js92
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/any.spec.js92
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.js306
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asin.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asinh.spec.js65
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan.spec.js80
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan2.spec.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atanh.spec.js87
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.js135
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.js742
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.js470
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.js192
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.js100
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.js131
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.js301
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.js135
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/harness.js208
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/bitcast.spec.js1275
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/builtin.js24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ceil.spec.js101
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/clamp.spec.js195
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cos.spec.js84
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cosh.spec.js68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.js250
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.js249
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.js250
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cross.spec.js113
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/degrees.spec.js95
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/determinant.spec.js137
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/distance.spec.js241
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dot.spec.js182
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdx.spec.js23
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.js22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.js21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdy.spec.js22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.js22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.js21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp.spec.js90
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp2.spec.js90
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/extractBits.spec.js337
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/faceForward.spec.js256
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.js350
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.js250
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/floor.spec.js96
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fma.spec.js113
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fract.spec.js103
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/frexp.spec.js475
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidth.spec.js21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.js21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.js21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/insertBits.spec.js386
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.js81
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ldexp.spec.js121
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/length.spec.js178
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log.spec.js89
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log2.spec.js89
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/max.spec.js165
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/min.spec.js164
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/mix.spec.js275
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/modf.spec.js661
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/normalize.spec.js137
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.js88
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.js55
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.js55
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.js60
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.js60
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pow.spec.js88
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.js70
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/radians.spec.js90
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reflect.spec.js180
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/refract.spec.js253
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.js250
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/round.spec.js79
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/saturate.spec.js100
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/select.spec.js253
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sign.spec.js109
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sin.spec.js84
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sinh.spec.js68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.js94
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sqrt.spec.js68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/step.spec.js87
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.js38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tan.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tanh.spec.js62
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.js160
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGather.spec.js270
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.js134
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.js185
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.js100
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.js65
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.js37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSample.spec.js273
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.js163
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.js145
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.js149
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.js136
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.js274
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureStore.spec.js122
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/transpose.spec.js158
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/trunc.spec.js75
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/utils.js45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.js38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/case_cache.js200
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/expression.js1436
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_arithmetic.spec.js43
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_assignment.spec.js112
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_conversion.spec.js174
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_logical.spec.js33
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.js44
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_conversion.spec.js301
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.js41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_conversion.spec.js257
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.js37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_complement.spec.js37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_conversion.spec.js196
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_complement.spec.js37
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_conversion.spec.js206
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/unary.js15
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/float_parse.spec.js131
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/call.spec.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/complex.spec.js42
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/eval_order.spec.js1007
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/for.spec.js271
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/harness.js312
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/if.spec.js102
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/loop.spec.js125
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/phony.spec.js135
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/return.spec.js56
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/switch.spec.js156
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/while.spec.js140
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/adjacent.spec.js272
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/atomicity.spec.js102
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/barrier.spec.js250
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/coherence.spec.js525
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/memory_model_setup.js1118
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/weak.spec.js429
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/padding.spec.js406
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access.spec.js480
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js607
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/compute_builtins.spec.js297
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/shared_structs.spec.js332
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shadow.spec.js406
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/increment_decrement.spec.js381
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/zero_init.spec.js546
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/types.js289
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/const_assert/const_assert.spec.js201
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/const.spec.js61
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/override.spec.js31
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/ptr_spelling.spec.js153
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/util.js163
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/var_access_mode.spec.js116
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/access/vector.spec.js223
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/binary/bitwise_shift.spec.js166
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/abs.spec.js54
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acos.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acosh.spec.js80
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asin.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asinh.spec.js82
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan.spec.js79
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan2.spec.js106
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atanh.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atomics.spec.js70
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/bitcast.spec.js393
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/ceil.spec.js75
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/clamp.spec.js57
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/const_override_validation.js202
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cos.spec.js77
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cosh.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/degrees.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp.spec.js102
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp2.spec.js102
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.js81
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/length.spec.js221
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log.spec.js76
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log2.spec.js76
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/modf.spec.js76
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/radians.spec.js76
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/round.spec.js84
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/saturate.spec.js76
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sign.spec.js79
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sin.spec.js77
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sinh.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sqrt.spec.js81
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/tan.spec.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/alias_analysis.spec.js202
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/restrictions.spec.js757
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/align.spec.js341
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/attribute.spec.js87
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/binary_ops.spec.js89
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/blankspace.spec.js65
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/break.spec.js84
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/builtin.spec.js144
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/comments.spec.js75
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const.spec.js57
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const_assert.spec.js38
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/diagnostic.spec.js201
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/discard.spec.js65
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/enable.spec.js70
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/identifiers.spec.js407
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/literal.spec.js302
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/must_use.spec.js269
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/pipeline_stage.spec.js155
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/semicolon.spec.js269
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/source.spec.js29
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/unary_ops.spec.js48
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/var_and_let.spec.js106
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/binding.spec.js140
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/builtins.spec.js277
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/entry_point.spec.js141
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group.spec.js140
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group_and_binding.spec.js171
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/id.spec.js170
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/interpolate.spec.js217
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/invariant.spec.js99
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/locations.spec.js382
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/size.spec.js212
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/util.js196
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/workgroup_size.spec.js300
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_validation_test.js177
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/alias.spec.js123
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/struct.spec.js99
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/vector.spec.js78
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/uniformity/uniformity.spec.js2444
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/shader/values.js91
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/binary_stream.js213
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/buffer.js23
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/check_contents.js272
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/color_space_conversion.js265
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/command_buffer_maker.js85
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/compare.js472
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/constants.js487
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/conversion.js1635
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/copy_to_texture.js192
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/create_elements.js82
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/device_pool.js414
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/floating_point.js5441
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/math.js2247
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/memory.js25
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/pretty_diff_tables.js51
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/prng.js125
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/reinterpret.js118
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/shader.js196
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture.js81
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/base.js243
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/data_generation.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/layout.js371
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/subresource.js68
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.js980
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.spec.js334
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_view.js201
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.js348
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.spec.js159
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/util/unions.js45
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/configure.spec.js426
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/context_creation.spec.js47
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getCurrentTexture.spec.js383
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getPreferredCanvasFormat.spec.js19
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.js481
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageBitmap.spec.js543
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageData.spec.js221
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/canvas.spec.js841
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/image.spec.js271
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/util.js58
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/video.spec.js119
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/external_texture/video.spec.js480
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.html.js34
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.https.html12
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace.html.js139
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html23
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex.html.js772
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_copy.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_draw.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_store.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_copy.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha.html.js177
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_copy.https.html21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_draw.https.html21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html21
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.html.js79
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html15
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/create-pattern-data-url.js23
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/gpu_ref_test.js26
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_clear-ref.html22
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html17
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html.js41
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_complex-ref.html26
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_opaque-ref.html26
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_premultiplied-ref.html26
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html25
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/resize_observer-ref.html90
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.html.js150
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.https.html24
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/util.js307
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.js83
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.spec.js35
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker_launcher.js18
-rw-r--r--testing/web-platform/mozilla/tests/websockets/bug1793935.any.js23
-rw-r--r--testing/web-platform/mozilla/tests/webtransport/bfcache/closed.tentative.https.html119
-rw-r--r--testing/web-platform/mozilla/tests/webtransport/bfcache/connected.tentative.https.html116
-rw-r--r--testing/web-platform/mozilla/tests/webtransport/bfcache/failed.tentative.https.html120
-rw-r--r--testing/web-platform/mozilla/tests/webtransport/bfcache/helpers.js17
-rw-r--r--testing/web-platform/mozilla/tests/webtransport/bfcache/ports.sub.js12
-rw-r--r--testing/web-platform/mozilla/tests/webtransport/bfcache/worker.js50
-rw-r--r--testing/web-platform/mozilla/tests/workers/2-mib-file.py7
-rw-r--r--testing/web-platform/mozilla/tests/workers/bug1674278-crash.html6
-rw-r--r--testing/web-platform/mozilla/tests/workers/bug1674278.js6
-rw-r--r--testing/web-platform/mozilla/tests/workers/modules/dedicated-worker-import-csp.html115
-rw-r--r--testing/web-platform/mozilla/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js17
-rw-r--r--testing/web-platform/mozilla/tests/workers/modules/resources/new-shared-worker-window.html19
-rw-r--r--testing/web-platform/mozilla/tests/workers/modules/resources/new-worker-window.html19
-rw-r--r--testing/web-platform/mozilla/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js20
-rw-r--r--testing/web-platform/mozilla/tests/workers/modules/shared-worker-import-csp.html123
-rw-r--r--testing/web-platform/mozilla/tests/workers/resources/worker.js129
-rw-r--r--testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html52
-rw-r--r--testing/web-platform/mozilla/tests/xml/parsedepth.html62
-rw-r--r--testing/web-platform/mozilla/tests/xml/responsexml-content-type.html49
1749 files changed, 205081 insertions, 0 deletions
diff --git a/testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html b/testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html
new file mode 100644
index 0000000000..01758fa179
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/audio-output/selectAudioOutput-user-activation-consumed.https.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<head>
+<title>Test selectAudioOutput() after user activation is consumed</title>
+<link rel="help" href="https://github.com/w3c/mediacapture-output/issues/107">
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+ await test_driver.bless('transient activation');
+ // SpecialPowers is used to consume user activation because the only
+ // spec-compliant Gecko API that consumes user activation is
+ // navigator.share(), which is disabled on CI versions of WINNT.
+ // https://searchfox.org/mozilla-central/rev/66547980e8e8ca583473c74f207cae5bac1ed541/testing/web-platform/meta/web-share/share-consume-activation.https.html.ini#4
+ const had_transient_activation =
+ SpecialPowers.wrap(document).consumeTransientUserGestureActivation();
+ assert_true(had_transient_activation,
+ 'should have had transient activation');
+ const p = navigator.mediaDevices.selectAudioOutput();
+ // Race a settled promise to check that the returned promise is already
+ // rejected.
+ await promise_rejects_dom(
+ t, 'InvalidStateError', Promise.race([p, Promise.resolve()]),
+ 'selectAudioOutput should have returned an already-rejected promise.');
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html b/testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html
new file mode 100644
index 0000000000..889ee9367f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/baselinecoverage/wpt_baselinecoverage.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>Baseline Coverage</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ test(function() {
+ assert_equals(true, true);
+ }, "Collecting basic baseline coverage for web-platform tests.");
+
+ async_test(function(t) {
+ function delayBaseline() {
+ return new Promise((c) => setTimeout(c, 30 * 1000));
+ }
+
+ delayBaseline().then(() => {
+ assert_equals(true, true);
+ t.done();
+ });
+ }, "Collecting delayed baseline coverage for web-platform tests.");
+</script>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.http.html
new file mode 100644
index 0000000000..c420b940f8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.http.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.https.html
new file mode 100644
index 0000000000..a7bb3de773
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/sharedworker-import.https.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.http.html
new file mode 100644
index 0000000000..482a41186d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.http.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- this is edited. We need to update it so that it is correctly generated if this gets adopted -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.https.html
new file mode 100644
index 0000000000..4d75fa1ccc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/script-src-self/worker-import.https.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- this is edited. We need to update it so that it is correctly generated if this gets adopted -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.http.html
new file mode 100644
index 0000000000..f06c11dd78
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.http.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.https.html
new file mode 100644
index 0000000000..1c9483fc05
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/sharedworker-import.https.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.http.html
new file mode 100644
index 0000000000..f8a9e51557
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.http.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.https.html
new file mode 100644
index 0000000000..7d6a82d8fc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.http-rp/worker-src-self/worker-import.https.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "http-rp",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.http.html
new file mode 100644
index 0000000000..f66bc9ed7c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.http.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.https.html
new file mode 100644
index 0000000000..bcd1f0f2ef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/sharedworker-import.https.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.http.html
new file mode 100644
index 0000000000..68a610d994
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.http.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- this is edited. We need to update it so that it is correctly generated if this gets adopted -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.https.html
new file mode 100644
index 0000000000..829e6e2b90
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/script-src-self/worker-import.https.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- this is edited. We need to update it so that it is correctly generated if this gets adopted -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'script-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.http.html
new file mode 100644
index 0000000000..4c6757c65f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.http.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.https.html
new file mode 100644
index 0000000000..2655a33036
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/sharedworker-import.https.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! Generated by `common/security-features/tools/generate.py --spec content-security-policy/` -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for sharedworker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "sharedworker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for sharedworker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.http.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.http.html
new file mode 100644
index 0000000000..281987acfe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.http.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- this is edited. We need to update it so that it is correctly generated if this gets adopted -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and keep-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and no-redirect redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-http origin and swap-origin redirection from http context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-http",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "http",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-http origin and swap-origin redirection from http context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.https.html b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.https.html
new file mode 100644
index 0000000000..4654856ada
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/gen/top.meta/worker-src-self/worker-import.https.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<!-- this is edited. We need to update it so that it is correctly generated if this gets adopted -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <meta http-equiv="Content-Security-Policy" content="worker-src 'self'">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/security-features/resources/common.sub.js"></script>
+ <script src="../../../generic/test-case.sub.js"></script>
+ </head>
+ <body>
+ <script>
+ TestCase(
+ [
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "allowed",
+ "origin": "same-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects allowed for worker-import to same-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "keep-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and keep-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "no-redirect",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and no-redirect redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "cross-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to cross-https origin and swap-origin redirection from https context."
+ },
+ {
+ "expectation": "blocked",
+ "origin": "same-https",
+ "redirection": "swap-origin",
+ "source_context_list": [],
+ "source_scheme": "https",
+ "subresource": "worker-import",
+ "subresource_policy_deliveries": [
+ {
+ "deliveryType": "meta",
+ "key": "contentSecurityPolicy",
+ "value": 'worker-src-self'
+ }
+ ],
+ "test_description": "Content Security Policy: Expects blocked for worker-import to same-https origin and swap-origin redirection from https context."
+ }
+ ],
+ new SanityChecker()
+ ).start();
+ </script>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/content-security-policy/generic/test-case.sub.js b/testing/web-platform/mozilla/tests/content-security-policy/generic/test-case.sub.js
new file mode 100644
index 0000000000..d9a6494dd3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/content-security-policy/generic/test-case.sub.js
@@ -0,0 +1,98 @@
+function TestCase(scenarios, sanityChecker) {
+ function runTest(scenario) {
+ sanityChecker.checkScenario(scenario, subresourceMap);
+
+ const urls = getRequestURLs(scenario.subresource,
+ scenario.origin,
+ scenario.redirection);
+
+ /** @type {Subresource} */
+ const subresource = {
+ subresourceType: scenario.subresource,
+ url: urls.testUrl,
+ policyDeliveries: scenario.subresource_policy_deliveries,
+ };
+
+ let violationEventResolve;
+ // Resolved with an array of securitypolicyviolation events.
+ const violationEventPromise = new Promise(resolve => {
+ violationEventResolve = resolve;
+ });
+
+ promise_test(async t => {
+ await xhrRequest(urls.announceUrl);
+
+ // Currently only requests from top-level Documents are tested
+ // (specified by `spec.src.json`) and thus securitypolicyviolation
+ // events are assumed to be fired on the top-level Document here.
+ // When adding non-top-level Document tests, securitypolicyviolation
+ // events should be caught in appropriate contexts.
+ const violationEvents = [];
+ const listener = e => { violationEvents.push(e); };
+ document.addEventListener('securitypolicyviolation', listener);
+
+ try {
+ // Send out the real resource request.
+ // This should tear down the key if it's not blocked.
+ const mainPromise = invokeRequest(subresource, scenario.source_context_list);
+ if (scenario.expectation === 'allowed') {
+ await mainPromise;
+ } else {
+ await mainPromise
+ .then(t.unreached_func('main promise resolved unexpectedly'))
+ .catch(_ => {});
+ }
+ } finally {
+ // Always perform post-processing/clean up for
+ // 'securitypolicyviolation' events and resolve
+ // `violationEventPromise`, to prevent timeout of the
+ // promise_test() below.
+
+ // securitypolicyviolation events are fired in a queued task in
+ // https://w3c.github.io/webappsec-csp/#report-violation
+ // so wait for queued tasks to run using setTimeout().
+ let timeout = 0;
+ if (scenario.subresource.startsWith('worklet-') &&
+ navigator.userAgent.includes("Firefox/")) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1808911
+ // In Firefox sometimes violations from Worklets are delayed.
+ timeout = 10;
+ }
+ await new Promise(resolve => setTimeout(resolve, timeout));
+
+ // Pass violation events to `violationEventPromise` (which will be tested
+ // in the subsequent promise_test()) and clean up the listener.
+ violationEventResolve(violationEvents);
+ document.removeEventListener('securitypolicyviolation', listener);
+ }
+
+ // Send request to check if the key has been torn down.
+ const assertResult = await xhrRequest(urls.assertUrl);
+
+ // Now check if the value has been torn down. If it's still there,
+ // we have blocked the request by content security policy.
+ assert_equals(assertResult.status, scenario.expectation,
+ "The resource request should be '" + scenario.expectation + "'.");
+
+ }, scenario.test_description);
+
+ promise_test(async _ => {
+ const violationEvents = await violationEventPromise;
+ if (scenario.expectation === 'allowed') {
+ assert_array_equals(violationEvents, [],
+ 'no violation events should be fired');
+ } else {
+ assert_equals(violationEvents.length, 1,
+ 'One violation event should be fired');
+ }
+ }, scenario.test_description + ": securitypolicyviolation");
+ } // runTest
+
+ function runTests() {
+ for (const scenario of scenarios) {
+ runTest(scenario);
+ }
+ }
+
+ return {start: runTests};
+}
diff --git a/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html
new file mode 100644
index 0000000000..69c1c4b7dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-document-reflow-count.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS Contain: Test content-visibility:hidden reflow counts</title>
+ <link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <body style="content-visibility: hidden;">
+ hello
+ </body>
+
+ <script>
+ let gUtils = SpecialPowers.getDOMWindowUtils(window);
+ let gTestContainer = document.getElementById("test");
+
+ function flushLayout() {
+ document.documentElement.offsetHeight;
+ }
+
+ function getReflowCount() {
+ flushLayout();
+ return gUtils.framesReflowed;
+ }
+
+ function runTestFunctionAndCountReflows(testFunction) {
+ const beforeCount = getReflowCount();
+ testFunction();
+ const afterCount = getReflowCount();
+ console.log(afterCount - beforeCount);
+ return afterCount - beforeCount;
+ }
+
+ test(() => {
+ flushLayout();
+
+ const reflows = runTestFunctionAndCountReflows(() => {
+ document.body.innerText = "something else";
+ });
+ assert_equals(reflows, 1, "Reflow only triggered on body.");
+ }, "Changing text of 'content-visibility: hidden' body only triggers a single reflow.");
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html
new file mode 100644
index 0000000000..c1484d9c54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility-hidden-reflow-count.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS Contain: Test content-visibility:hidden reflow counts</title>
+ <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1746098">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <style>
+ .container {
+ content-visibility: visible;
+ contain: strict;
+ }
+ .flex {
+ display: flex;
+ }
+ .grid {
+ display: grid;
+ grid: repeat(2, 60px) / auto-flow 80px;
+ }
+ </style>
+
+ <div id="test"></div>
+
+ <script>
+ let gUtils = SpecialPowers.getDOMWindowUtils(window);
+ let gTestContainer = document.getElementById("test");
+
+ function setupContainerWithStrictContainment() {
+ const container = document.createElement("div");
+ container.classList.add("container");
+ gTestContainer.innerHTML = "";
+ gTestContainer.appendChild(container);
+ return container;
+ }
+
+ function flushLayout() {
+ document.documentElement.offsetHeight;
+ }
+
+ function getReflowCount() {
+ flushLayout();
+ return gUtils.framesReflowed;
+ }
+
+ function runTestFunctionAndCountReflows(testFunction, container) {
+ const beforeCount = getReflowCount();
+ testFunction(container);
+ const afterCount = getReflowCount();
+ return afterCount - beforeCount;
+ }
+
+ function assertContentVisibilityHiddenHasFewerReflows(testSetup, testFunction) {
+ let container = setupContainerWithStrictContainment();
+ testSetup(container);
+ flushLayout();
+
+ const visibleReflows = runTestFunctionAndCountReflows(testFunction, container);
+
+ container = setupContainerWithStrictContainment();
+ testSetup(container);
+ container.style.contentVisibility = "hidden";
+ flushLayout();
+
+ const hiddenReflows = runTestFunctionAndCountReflows(testFunction, container);
+ assert_less_than(hiddenReflows, visibleReflows,
+ "Style / layout changes in hidden content resulted in fewer reflows than visible content.");
+ }
+
+ test(() => {
+ assertContentVisibilityHiddenHasFewerReflows(
+ (container) => {
+ const div = document.createElement("div");
+ div.innerText = "Test Content";
+ container.appendChild(div);
+ },
+ (container) => {
+ container.children[0].style.width = "100px";
+ container.children[0].style.height = "100px";
+ });
+ }, `Avoiding layout while modifying a simple div's style.`);
+
+ test(() => {
+ assertContentVisibilityHiddenHasFewerReflows(
+ (container) => {
+ container.classList.add("flex");
+
+ const flexContainer = document.createElement("div");
+ flexContainer.classList.add("flex");
+ container.appendChild(flexContainer);
+
+ container.appendChild(document.createElement("div"));
+ },
+ (container) => {
+ container.children[0].style.flexDirection = "row-reverse";
+ }
+ );
+ }, `Avoiding layout while modifying a div with flex display mode.`);
+
+ test(() => {
+ assertContentVisibilityHiddenHasFewerReflows(
+ (container) => {
+ container.classList.add("grid");
+
+ const gridChild = document.createElement("div");
+ gridChild.style.display = "grid";
+ container.appendChild(gridChild);
+
+ container.appendChild(document.createElement("div"));
+ },
+ (container) => {
+ container.children[0].style.rowGap = "30px";
+ },
+ );
+ }, `Avoiding layout while modifying a div with grid display mode.`);
+
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html
new file mode 100644
index 0000000000..57715be8a3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates-stop-ticking.html
@@ -0,0 +1,139 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Content Visibility: stop ticking after relevancy updates</title>
+<!--
+ Copied from testing/web-platform/tests/css/css-contain/content-visibility/content-visibility-auto-relevancy-updates.html
+-->
+<link rel="author" title="Frédéric Wang" href="mailto:fwang@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel="help" href="https://drafts.csswg.org/css-contain/#relevant-to-the-user">
+<link rel="help" href="https://drafts.csswg.org/css-contain/#cv-notes">
+<meta name="assert" content="Verify relevancy is properly updated for content-visibility: auto elements and refresh driver stops ticking after such update.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+ #spacer {
+ height: 300vh;
+ }
+ #contentVisibilityAuto {
+ content-visibility: auto;
+ border: solid;
+ }
+</style>
+<div>
+ <div id="log"></div>
+ <div tabindex="1" id="spacer"></div>
+ <div tabindex="2" id="contentVisibilityAuto">
+ <span>Hello, World!</span>
+ </div>
+</div>
+
+<script>
+function hasPendingTick() {
+ return SpecialPowers.wrap(window).windowUtils.refreshDriverHasPendingTick;
+}
+
+// See comment in layout/base/tests/test_bug1756118.html about why the timeouts
+// etc.
+async function expectTicksToStop() {
+ for (let i = 0; i < 100; i++) {
+ await new Promise(r => setTimeout(r, 8));
+ if(!hasPendingTick()) {
+ break;
+ }
+ }
+ assert_false(hasPendingTick(), "refresh driver should have eventually stopped ticking");
+}
+
+function tick() {
+ return new Promise(r => {
+ requestAnimationFrame(() => requestAnimationFrame(r));
+ });
+}
+
+function contentVisibilityAutoElementIsRelevant() {
+ // A content-visibility: auto element that is not relevant skips its contents,
+ // which do not contribute to the result of innerText.
+ return contentVisibilityAuto.innerText.length > 0;
+}
+
+function clearRelevancyReasons() {
+ window.scrollTo(0, 0);
+ spacer.focus({preventScroll: true});
+ window.getSelection().empty();
+}
+
+promise_test(async function(t) {
+ // Wait for page load.
+ await new Promise(resolve => { window.addEventListener("load", resolve); });
+
+ // Register cleanup method to reset relevancy.
+ t.add_cleanup(clearRelevancyReasons);
+
+ // Element should initially not be relevant and ticking should have stopped.
+ await SpecialPowers.pushPrefEnv({'set':
+ [['layout.keep_ticking_after_load_ms', 0]]});
+ await tick();
+ await expectTicksToStop();
+ assert_false(contentVisibilityAutoElementIsRelevant(), "initial relevancy");
+
+ // Make element close to the viewport.
+ contentVisibilityAuto.firstElementChild.scrollIntoView();
+ await tick();
+ await expectTicksToStop();
+ assert_true(contentVisibilityAutoElementIsRelevant(), "close to viewport");
+
+ // Scroll away from the element again.
+ window.scrollTo(0, 0);
+ await tick();
+ await expectTicksToStop();
+ assert_false(contentVisibilityAutoElementIsRelevant(), "far from viewport");
+}, "Relevancy updated after changing proximity to the viewport.");
+
+promise_test(async function(t) {
+ // Register cleanup method to reset relevancy.
+ t.add_cleanup(clearRelevancyReasons);
+
+ // Element should initially not be relevant and no ticking be in progress.
+ await tick();
+ await expectTicksToStop();
+ assert_false(contentVisibilityAutoElementIsRelevant(), "initial relevancy");
+
+ // Focus the element.
+ contentVisibilityAuto.focus({preventScroll: true});
+ await tick();
+ await expectTicksToStop();
+ assert_true(contentVisibilityAutoElementIsRelevant(), "focused");
+
+ // Unfocus the element again.
+ spacer.focus({preventScroll: true});
+ await tick();
+ await expectTicksToStop();
+ assert_false(contentVisibilityAutoElementIsRelevant(), "unfocused");
+}, "Relevancy updated after being focused/unfocused.");
+
+promise_test(async function(t) {
+ // Register cleanup method to reset relevancy.
+ t.add_cleanup(clearRelevancyReasons);
+
+ // Element should initially not be relevant and no ticking be in progress.
+ await tick();
+ await expectTicksToStop();
+ assert_false(contentVisibilityAutoElementIsRelevant(), "initial relevancy");
+
+ // Select the contents of the element.
+ window.getSelection().selectAllChildren(contentVisibilityAuto);
+ await tick();
+ await expectTicksToStop();
+ assert_true(contentVisibilityAutoElementIsRelevant(), "selected");
+
+ // Unselect the contents of the element.
+ window.getSelection().empty();
+ await tick();
+ await expectTicksToStop();
+ assert_false(contentVisibilityAutoElementIsRelevant(), "unselected");
+}, "Relevancy updated after being selected/unselected.");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/css-grid/alignment/grid-item-aspect-ratio-justify-self-003.html b/testing/web-platform/mozilla/tests/css/css-grid/alignment/grid-item-aspect-ratio-justify-self-003.html
new file mode 100644
index 0000000000..af0fc54e1d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-grid/alignment/grid-item-aspect-ratio-justify-self-003.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
+<!-- NOTE(dholbert) This should maybe move to the shared WPT directory, but I'm
+ putting it in the internal directory for now because tables with captions
+ and aspect-ratio behave extremely differently in each engine right now,
+ and I'm not sure the behavior is well-specified. -->
+<meta name="assert" content="For a grid item with display:table, a caption, an 'aspect-ratio', a resolvable percent height, and a non-stretching justify-self, the item's inline-size should be resolved based on the height and 'aspect-ratio' (after subtracting away the space needed for the caption)">
+<style>
+ .group { margin-bottom: 20px;}
+ .group.orthog .item { writing-mode: vertical-rl }
+ .grid {
+ height: 32px;
+ width: 24px;
+ display: inline-grid;
+ border: 2px solid black;
+ vertical-align: top;
+ background: cyan;
+ }
+
+ .item {
+ display: table;
+ height: 100%;
+ background: fuchsia;
+ box-sizing: border-box;
+ aspect-ratio: 1/2;
+ }
+ cap {
+ display: table-caption;
+ height: 4px;
+ width: 6px;
+ background: orange;
+ }
+ .center { justify-self: center; }
+ .start { justify-self: start; }
+ .end { justify-self: end; }
+ .self-start { justify-self: self-start; }
+ .self-end { justify-self: self-end; }
+ .flex-start { justify-self: flex-start; }
+ .flex-end { justify-self: flex-end; }
+ .left { justify-self: left; }
+ .right { justify-self: right; }
+ .normal { justify-self: normal; }
+ .stretch { justify-self: stretch; }
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.item')">
+ <div class="group">
+ <div class="grid">
+ <div class="item start"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item end"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item self-start"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item self-end"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item flex-start"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item flex-end"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item left"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item right"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item center"
+ data-expected-width="14" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item normal"
+ data-expected-width="24" data-expected-height="52"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item stretch"
+ data-expected-width="24" data-expected-height="52"><cap></cap></div>
+ </div>
+ </div>
+
+ <!-- For these ones, the item's writing-mode will be orthogonal to the
+ grid container's writing-mode -->
+ <div class="group orthog">
+ <div class="grid">
+ <div class="item start"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item end"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item self-start"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item self-end"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item flex-start"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item flex-end"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item left"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item right"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item center"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item normal"
+ data-expected-width="22" data-expected-height="32"><cap></cap></div>
+ </div>
+ <div class="grid">
+ <div class="item stretch"
+ data-expected-width="24" data-expected-height="32"><cap></cap></div>
+ </div>
+ </div>
+</body>
diff --git a/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-005.html b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-005.html
new file mode 100644
index 0000000000..d4c754c910
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-005.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<!-- TODO: Remove this test once bug 1871719 is addressed. This test is just a
+ modified copy of a more-robust WPT test, with several forms of padding
+ removed to provide test coverage while working around bug 1871719. -->
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/">
+<style>
+.item {
+ inline-size: 40px;
+ box-sizing: border-box;
+ border: solid 5px hotpink;
+ line-height: 0;
+ margin-block-start: 3px;
+ margin-block-end: 5px;
+}
+.small {
+ width: 20px;
+ height: 20px;
+ border: solid 5px cyan;
+}
+.first {
+ align-self: baseline;
+}
+.last {
+ align-self: last baseline;
+}
+span {
+ width: 20px;
+ height: 20px;
+ box-sizing: border-box;
+ border: solid 5px orange;
+ display: inline-block;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.item')">
+
+<div style="position: relative; display: grid; grid-template: 150px 150px 150px / 100px 100px 100px 100px;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 3;
+ grid-template: subgrid / subgrid;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 2;
+ grid-template: subgrid / subgrid;">
+ <div data-offset-y="3" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="95" class="item last">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="153" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="245" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-y="303" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="395" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-y="8" class="item small first"></div>
+ <div data-offset-y="120" class="item small last"></div>
+ <div data-offset-y="158" class="item small first"></div>
+ <div data-offset-y="270" class="item small last"></div>
+ <div data-offset-y="308" class="item small first"></div>
+ <div data-offset-y="420" class="item small last"></div>
+</div>
diff --git a/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-006.html b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-006.html
new file mode 100644
index 0000000000..4b9cd746da
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-006.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<!-- TODO: Remove this test once bug 1871719 is addressed. This test is just a
+ modified copy of a more-robust WPT test, with several forms of padding
+ removed to provide test coverage while working around bug 1871719. -->
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/">
+<style>
+.item {
+ inline-size: 40px;
+ box-sizing: border-box;
+ border: solid 5px hotpink;
+ line-height: 0;
+ margin-block-start: 3px;
+ margin-block-end: 5px;
+}
+.small {
+ width: 30px;
+ height: 30px;
+ border: solid 5px cyan;
+}
+.first {
+ align-self: baseline;
+}
+.last {
+ align-self: last baseline;
+}
+span {
+ width: 20px;
+ height: 20px;
+ box-sizing: border-box;
+ border: solid 5px orange;
+ display: inline-block;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.item')">
+
+<div style="writing-mode: vertical-rl; width: 600px; position: relative; display: grid; grid-template: 150px 150px 150px / 100px 100px 100px 100px;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 3;
+ grid-template: subgrid / subgrid;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 2;
+ grid-template: subgrid / subgrid;">
+ <div data-offset-x="547" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="455" class="item last">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="397" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="305" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-x="247" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="155" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-x="567" class="item small first"></div>
+ <div data-offset-x="455" class="item small last"></div>
+ <div data-offset-x="417" class="item small first"></div>
+ <div data-offset-x="305" class="item small last"></div>
+ <div data-offset-x="267" class="item small first"></div>
+ <div data-offset-x="155" class="item small last"></div>
+</div>
diff --git a/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-007.html b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-007.html
new file mode 100644
index 0000000000..43739a65be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-007.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<!-- TODO: Remove this test once bug 1871719 is addressed. This test is just a
+ modified copy of a more-robust WPT test, with several forms of padding
+ removed to provide test coverage while working around bug 1871719. -->
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/">
+<style>
+.item {
+ inline-size: 40px;
+ box-sizing: border-box;
+ border: solid 5px hotpink;
+ line-height: 0;
+ /* Note: the original test has margins here on every .item that we don't
+ handle properly for the items-in-the-subgrid for some reason, but we do
+ handle the margins properly on the items that aren't in a subgrid, so
+ I've just moved the margin declaration over to .small which
+ more-specifically-targets those items where we do handle things properly.
+ */
+}
+.small {
+ width: 20px;
+ height: 20px;
+ border: solid 5px cyan;
+ margin-block-start: 3px;
+ margin-block-end: 5px;
+}
+.first {
+ align-self: baseline;
+}
+.last {
+ align-self: last baseline;
+}
+.item.small.first {
+ block-size: 50px;
+}
+.item.small.last {
+ block-size: 100px;
+}
+span {
+ width: 20px;
+ height: 20px;
+ box-sizing: border-box;
+ border: solid 5px orange;
+ display: inline-block;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.item')">
+
+<div style="position: relative; display: grid; grid-template: auto auto auto / 100px 100px 100px 100px;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 3;
+ grid-template: subgrid / subgrid;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 2;
+ grid-template: subgrid / subgrid;">
+ <div data-offset-y="28" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="58" class="item last">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="136" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="166" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-y="244" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-y="274" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-y="3" class="item small first"></div>
+ <div data-offset-y="3" class="item small last"></div>
+ <div data-offset-y="111" class="item small first"></div>
+ <div data-offset-y="111" class="item small last"></div>
+ <div data-offset-y="219" class="item small first"></div>
+ <div data-offset-y="219" class="item small last"></div>
+</div>
diff --git a/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-008.html b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-008.html
new file mode 100644
index 0000000000..dbffc18ad0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-008.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<!-- TODO: Remove this test once bug 1871719 is addressed. This test is just a
+ modified copy of a more-robust WPT test, with several forms of padding
+ removed to provide test coverage while working around bug 1871719. -->
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/">
+<style>
+.item {
+ inline-size: 40px;
+ box-sizing: border-box;
+ border: solid 5px hotpink;
+ line-height: 0;
+ margin-block-start: 3px;
+ margin-block-end: 5px;
+}
+.small {
+ width: 20px;
+ height: 20px;
+ border: solid 5px cyan;
+}
+.first {
+ align-self: baseline;
+}
+.last {
+ align-self: last baseline;
+}
+.item.small.first {
+ block-size: 50px;
+}
+.item.small.last {
+ block-size: 100px;
+}
+span {
+ width: 20px;
+ height: 20px;
+ box-sizing: border-box;
+ border: solid 5px orange;
+ display: inline-block;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.item')">
+
+<div style="writing-mode: vertical-rl; width: 600px; position: relative; display: grid; grid-template: auto auto auto / 100px 100px 100px 100px;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 3;
+ grid-template: subgrid / subgrid;">
+ <div style="display: grid;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 2;
+ grid-template: subgrid / subgrid;">
+ <div data-offset-x="537" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="440" class="item last">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="337" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="240" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-x="137" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="40" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-x="547" class="item small first"></div>
+ <div data-offset-x="405" class="item small last"></div>
+ <div data-offset-x="347" class="item small first"></div>
+ <div data-offset-x="205" class="item small last"></div>
+ <div data-offset-x="147" class="item small first"></div>
+ <div data-offset-x="5" class="item small last"></div>
+</div>
diff --git a/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-009.html b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-009.html
new file mode 100644
index 0000000000..1e5e1d7e4e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-grid/subgrid/subgrid-baseline-009.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<!-- TODO: Remove this test once bug 1871719 is addressed. This test is just a
+ modified copy of a more-robust WPT test, with several forms of padding
+ removed to provide test coverage while working around bug 1871719. -->
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/">
+<style>
+.item {
+ writing-mode: vertical-rl;
+ block-size: 80px;
+ box-sizing: border-box;
+ border: solid 5px hotpink;
+ line-height: 0;
+ margin-block-start: 10px;
+ margin-block-end: 15px;
+}
+.small {
+ width: 20px;
+ height: 20px;
+ border: solid 5px cyan;
+}
+.first {
+ justify-self: baseline;
+}
+.last {
+ justify-self: last baseline;
+}
+span {
+ width: 20px;
+ height: 20px;
+ box-sizing: border-box;
+ border: solid 5px orange;
+ display: inline-block;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<body onload="checkLayout('.item')">
+
+<div style="width: 600px; position: relative; display: grid; grid-auto-flow: column; grid-template: 100px 100px 100px 100px / auto auto auto;">
+ <div style="display: grid;
+ grid-auto-flow: column;
+ grid-column: 1 / span 3;
+ grid-row: 1 / span 2;
+ grid-template: subgrid / subgrid;">
+ <div style="display: grid;
+ direction: rtl;
+ grid-auto-flow: column;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 2;
+ grid-template: subgrid / subgrid;">
+ <div data-offset-x="305" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="215" class="item last">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="105" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="15" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-x="505" class="item first">
+ <span></span><br><span></span>
+ </div>
+ <div data-offset-x="415" class="item last">
+ <span></span><br><span></span>
+ </div>
+ </div>
+ <div data-offset-x="170" class="item small first"></div>
+ <div data-offset-x="60" class="item small last"></div>
+ <div data-offset-x="370" class="item small first"></div>
+ <div data-offset-x="260" class="item small last"></div>
+ <div data-offset-x="570" class="item small first"></div>
+ <div data-offset-x="460" class="item small last"></div>
+</div>
diff --git a/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-events.html b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-events.html
new file mode 100644
index 0000000000..7b07c5d837
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-events.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1866773">
+<style>
+#container {
+ width: 100px;
+ height: 100px;
+ border: 1px;
+}
+#child {
+ height: 200px;
+}
+</style>
+<div id="container">
+ <div id="child"></div>
+</div>
+<script>
+let container = document.getElementById("container");
+let child = document.getElementById("child");
+let InspectorUtils = SpecialPowers.wrap(window).InspectorUtils;
+
+promise_test(async function(t) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["ui.useOverlayScrollbars", 1],
+ ["ui.scrollbarFadeDuration", 200],
+ ]
+ });
+
+ for (let type of ["transitionstart", "transitionend"]) {
+ container.addEventListener(type, t.unreached_func(`Shouldn't see ${type} event on #container`));
+ }
+ // This creates scrollbars and triggers activation.
+ container.style.overflow = "scroll";
+ container.getBoundingClientRect();
+ let scrollbars = InspectorUtils.getChildrenForNode(container, true, true).filter(child => SpecialPowers.wrap(child).nodeName.toLowerCase() == "scrollbar");
+ assert_equals(scrollbars.length, 2, "Should have two scrollbars");
+ let {resolve, promise} = Promise.withResolvers();
+ {
+ let expectedEvents = 2;
+ for (let scrollbar of scrollbars) {
+ SpecialPowers.wrap(scrollbar).addEventListener("transitionend", function() {
+ assert_true(expectedEvents > 0, "Should see an expected transition");
+ if (--expectedEvents) {
+ resolve();
+ }
+ });
+ }
+ }
+ await promise;
+ await new Promise(r => requestAnimationFrame(r));
+ // FIXME: This doesn't seem to be done automatically by the test harness in WPT
+ await SpecialPowers.popPrefEnv();
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html
new file mode 100644
index 0000000000..aa4ed4667b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-gutter-reflow-counts-001.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>CSS Overflow: Test scrollbar-gutter reflow counts</title>
+ <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1746098">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <style>
+ #target {
+ inline-size: 200px;
+ block-size: 100px;
+ background: lightgray;
+ overflow: auto;
+ }
+
+ #targetChild {
+ inline-size: 100%;
+ block-size: 100%;
+ background: orange;
+ }
+ </style>
+
+ <p>Here is a scroll contaier for testing:</p>
+ <div id="target">
+ <div id="targetChild"></div>
+ </div>
+
+ <script>
+ let gUtils = SpecialPowers.getDOMWindowUtils(window);
+ let gTarget = document.getElementById("target");
+ let gTargetChild = document.getElementById("targetChild");
+
+ function getReflowCount()
+ {
+ document.documentElement.offsetHeight; // flush layout
+ return gUtils.framesReflowed;
+ }
+
+ function cleanUp() {
+ gTarget.style.writingMode = "";
+ gTarget.style.scrollbarGutter = "";
+ gTargetChild.style.blockSize = "";
+ }
+
+ function tweakStyleAndCountReflows(aAddStyle, aAddScrollbarGutter)
+ {
+ let beforeCount = getReflowCount();
+ if (aAddScrollbarGutter) {
+ gTarget.style.scrollbarGutter = "stable";
+ }
+ aAddStyle();
+ let afterCount = getReflowCount();
+ cleanUp();
+
+ let numReflows = afterCount - beforeCount;
+ assert_greater_than(numReflows, 0, "We should've reflowed *something* after changing styles:");
+ return numReflows;
+ }
+
+ let gTestCases = [
+ {
+ name : "Enlarge the child's block-size to 200%",
+ addStyle : function () {
+ gTargetChild.style.blockSize = "200%";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 300px",
+ addStyle : function () {
+ gTargetChild.style.blockSize = "300px";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 200% in a vertical-lr scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-lr";
+ gTargetChild.style.blockSize = "200%";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 300px in a vertical-lr scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-lr";
+ gTargetChild.style.blockSize = "300px";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 200% in a vertical-rl scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-rl";
+ gTargetChild.style.blockSize = "200%";
+ },
+ },
+ {
+ name : "Enlarge the child's block-size to 300px in a vertical-rl scroll container",
+ addStyle : function () {
+ gTarget.style.writingMode = "vertical-rl";
+ gTargetChild.style.blockSize = "300px";
+ },
+ },
+ ];
+
+ for (let testcase of gTestCases) {
+ test(function () {
+ let numTestReflows = tweakStyleAndCountReflows(testcase.addStyle, true);
+ let numReferenceReflows = tweakStyleAndCountReflows(testcase.addStyle, false);
+ assert_less_than(numTestReflows, numReferenceReflows,
+ "A scroll container with 'scrollbar-gutter:stable' should have less reflow counts:");
+ }, testcase.name)
+ }
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos-ref.html b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos-ref.html
new file mode 100644
index 0000000000..e1414f1f66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>CSS Overflow Reference: Root element's scrollbar on the left edge is accounted for in fixed-pos positioning</title>
+<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<style>
+:root {
+ overflow: scroll;
+}
+body {
+ margin: 0;
+}
+.fixed {
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: absolute;
+}
+</style>
+
+<div class="fixed" style="left: 0"></div>
+<div class="fixed" style="right: 0"></div>
diff --git a/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos.html b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos.html
new file mode 100644
index 0000000000..5ecbad1b0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/css-overflow/scrollbar-left-fixedpos.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>CSS Overflow Test: Root element's scrollbar on the left edge is accounted for in fixed-pos positioning</title>
+<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=728807">
+<link rel="match" href="scrollbar-left-fixedpos-ref.html">
+<style>
+:root {
+ overflow: scroll;
+}
+body {
+ margin: 0;
+}
+.fixed {
+ width: 100px;
+ height: 100px;
+ background: green;
+ position: fixed;
+}
+</style>
+
+<div class="fixed" style="left: 0"></div>
+<div class="fixed" style="right: 0"></div>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html
new file mode 100644
index 0000000000..1fc00e7e39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print-ref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<div>PASS</div>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html
new file mode 100644
index 0000000000..8e9172956c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/media-print-change-print.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<link rel=match href="media-print-change-print-ref.html">
+<div id="target">FAIL</div>
+<script>
+ matchMedia("print").addEventListener("change", function() {
+ document.getElementById("target").innerHTML = "PASS";
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/moz-transform-exists.html b/testing/web-platform/mozilla/tests/css/cssom/moz-transform-exists.html
new file mode 100644
index 0000000000..b46238863c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/moz-transform-exists.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: MozTransform exists in Gecko for compat reasons</title>
+<meta charset="utf-8">
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ let el = document.documentElement;
+
+ assert_true("MozTransform" in el.style, "MozTransform exists");
+ assert_true("MozTransformOrigin" in el.style, "MozTransformOrigin also exists");
+
+ el.style.MozTransform = "scale(1)";
+ assert_not_equals(el.MozTransform, "", "MozTransform sets the value");
+ el.style.MozTransformOrigin = "0 0";
+ assert_not_equals(el.MozTransformOrigin, "", "MozTransformOrigin sets the value");
+
+ assert_equals(el.transform, el.MozTransform, "MozTransform is an alias of transform");
+ assert_equals(el.transformOrigin, el.MozTransformOrigin, "MozTransformOrigin is an alias of transformOrigin");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html b/testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html
new file mode 100644
index 0000000000..695bf8f34b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/cssom/window_size_rounding.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<pre>
+<script>
+test(function() {
+ let originalWidth = window.innerWidth;
+ let originalHeight = window.innerHeight;
+
+ assert_equals(window.devicePixelRatio, 1, `precondition: ${originalWidth}x${originalHeight}`);
+
+ // This precondition holds because of:
+ // https://searchfox.org/mozilla-central/rev/50215d649d4854812837f1343e8f47bd998dacb5/browser/base/content/browser.js#1717
+ //
+ // But if this test starts failing you can just update the assert and the
+ // factor below accordingly so that the asserts keep passing.
+ assert_equals(originalWidth, 1280, "precondition");
+
+ // Set a fractional scale factor that guarantees that we get a fractional innerWidth
+ const kFactor = 1.5;
+ const kOneAppUnit = 1 / 60;
+
+ SpecialPowers.setFullZoom(window, kFactor);
+ assert_approx_equals(window.devicePixelRatio, kFactor, kOneAppUnit);
+ assert_not_equals(window.innerWidth, originalWidth);
+ assert_not_equals(window.innerHeight, originalHeight);
+ if (SpecialPowers.getBoolPref("dom.innerSize.rounded")) {
+ assert_equals(window.innerWidth, Math.round(originalWidth / kFactor));
+ assert_equals(window.innerHeight, Math.round(originalHeight / kFactor));
+ } else {
+ assert_not_equals(window.innerWidth, Math.round(window.innerWidth));
+ }
+ SpecialPowers.setFullZoom(window, 1); // Restore zoom so results can be seen fine...
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html b/testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html
new file mode 100644
index 0000000000..67fc0af389
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/file-selector-button-margin-notref.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<input type=file>
diff --git a/testing/web-platform/mozilla/tests/css/file-selector-button-margin.html b/testing/web-platform/mozilla/tests/css/file-selector-button-margin.html
new file mode 100644
index 0000000000..46c1bd0e3e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/file-selector-button-margin.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<title>CSS Test: margin can be used to shrink the spacing between the file selector button and its label</title>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1673895">
+<link rel="mismatch" href="file-selector-margin-notref.html">
+<style>
+::file-selector-button {
+ margin: 0;
+}
+</style>
+<input type=file>
diff --git a/testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print-ref.html b/testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print-ref.html
new file mode 100644
index 0000000000..ad2922d8a1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<style>
+ body { margin: 0 }
+</style>
+<iframe frameborder=0 scrolling=no src="resources/iframe-os-text-scale-inner.html"></iframe>
diff --git a/testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print.sub.html b/testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print.sub.html
new file mode 100644
index 0000000000..d0647da9b3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/iframe-os-text-scale-print.sub.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<link rel=match href="iframe-os-text-scale-print-ref.html">
+<style>
+ body { margin: 0 }
+</style>
+<iframe frameborder=0 scrolling=no src="//{{hosts[alt][www]}}:{{ports[http][0]}}{{location[path]}}/../resources/iframe-os-text-scale-inner.html"></iframe>
diff --git a/testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html b/testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html
new file mode 100644
index 0000000000..06424705ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/mediaqueries/mq-gamut-resist-fingerprinting.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<title>Test: color-gamut only matches sRGB when resisting fingerprinting</title>
+<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
+<!--
+ This test is trivial as long as Firefox only matches the sRGB color-gamut media
+ query. Once Firefox can match additional color-gamuts, though, we should still
+ only match sRGB when resisting fingerprinting.
+-->
+<style>
+div {
+ width: 100px;
+ height: 100px;
+}
+
+div.with-gamut {
+ background-color: red;
+}
+
+div.without-gamuts {
+ background-color: green;
+}
+
+@media (color-gamut: srgb) {
+ div.with-gamut {
+ background-color: green;
+ }
+}
+
+@media (color-gamut: p3), (color-gamut: rec2020) {
+ div.without-gamuts {
+ background-color: red;
+ }
+}
+</style>
+
+<p>Test passes if there are two filled green squares and <strong>no red</strong>.
+
+<div class=with-gamut></div>
+<p></p>
+<div class=without-gamuts></div>
diff --git a/testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html b/testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html
new file mode 100644
index 0000000000..b926f81af4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/quirks-invalidation-standard-sheet.html
@@ -0,0 +1,14 @@
+<!-- quirks, intentionally -->
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div class="mixedCase"></div>
+<script>
+ test(function() {
+ const kLime = "rgb(0, 255, 0)";
+
+ let div = document.querySelector("div");
+ assert_not_equals(getComputedStyle(div).color, kLime);
+ SpecialPowers.DOMWindowUtils.loadSheetUsingURIString('data:text/css,.mixedCase{color:lime}', 1)
+ assert_equals(getComputedStyle(div).color, kLime);
+ }, "Invalidation of quirks documents when standard sheets are inserted works properly")
+</script>
diff --git a/testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht b/testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht
new file mode 100644
index 0000000000..9b647491e9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/reference/ref-filled-green-100px-square.xht
@@ -0,0 +1,19 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>CSS Reftest Reference</title>
+ <style type="text/css"><![CDATA[
+ div {
+ background-color: green;
+ height: 100px;
+ width: 100px;
+ }
+ ]]></style>
+ </head>
+ <body>
+ <p>Test passes if there are two filled green squares and <strong>no red</strong>.</p>
+ <div></div>
+ <p></p>
+ <div></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/css/resources/iframe-os-text-scale-inner.html b/testing/web-platform/mozilla/tests/css/resources/iframe-os-text-scale-inner.html
new file mode 100644
index 0000000000..be51cc957c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/css/resources/iframe-os-text-scale-inner.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<style>
+body {
+ background-color: green;
+ height: 100vh;
+ width: 100vw;
+}
+</style>
+TEST
diff --git a/testing/web-platform/mozilla/tests/dom/classList.html b/testing/web-platform/mozilla/tests/dom/classList.html
new file mode 100644
index 0000000000..21d79f49e3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/classList.html
@@ -0,0 +1,526 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for the classList element attribute</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="content"></div>
+<script>
+// This should be the same as dom/nodes/Element-classlist.html in the upstream
+// tests! We have a couple of bits changed, which are marked with comments
+// "LOCAL MODIFICATION". Do not change this without changing the upstream test
+// as well! Merging upstream changes here occasionally might also be nice.
+
+const SVG_NS = "http://www.w3.org/2000/svg";
+const XHTML_NS = "http://www.w3.org/1999/xhtml"
+const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+
+// BEGIN LOCAL MODIFICATION: The spec does not have the onAttrModified event
+// and does not want it, but we still support it.
+var gMutationEvents = [];
+
+function onAttrModified(event) {
+ assert_equals(event.attrName, "class", "mutation on unexpected attribute");
+
+ gMutationEvents.push({
+ attrChange: event.attrChange,
+ prevValue: event.prevValue,
+ newValue: event.newValue,
+ });
+}
+// END LOCAL MODIFICATION
+
+function setClass(e, newVal) {
+ if (newVal === null) {
+ e.removeAttribute("class");
+ } else {
+ e.setAttribute("class", newVal);
+ }
+}
+
+function checkModification(e, funcName, args, expectedRes, before, after,
+ expectedException, desc) {
+ if (!Array.isArray(args)) {
+ args = [args];
+ }
+
+ test(function() {
+ var shouldThrow = typeof(expectedException) === "string";
+ if (shouldThrow) {
+ // If an exception is thrown, the class attribute shouldn't change.
+ after = before;
+ }
+ setClass(e, before);
+
+ // BEGIN LOCAL MODIFICATION
+ gMutationEvents = [];
+ e.addEventListener("DOMAttrModified", onAttrModified);
+ // END LOCAL MODIFICATION
+ var obs;
+ // If we have MutationObservers available, do some checks to make
+ // sure attribute sets are happening at sane times.
+ if (self.MutationObserver) {
+ obs = new MutationObserver(() => {});
+ obs.observe(e, { attributes: true });
+ }
+ if (shouldThrow) {
+ assert_throws_dom(expectedException, function() {
+ var list = e.classList;
+ var res = list[funcName].apply(list, args);
+ });
+ } else {
+ var list = e.classList;
+ var res = list[funcName].apply(list, args);
+ }
+ if (obs) {
+ var mutationRecords = obs.takeRecords();
+ obs.disconnect();
+ if (shouldThrow) {
+ assert_equals(mutationRecords.length, 0,
+ "There should have been no mutation");
+ } else if (funcName == "replace") {
+ assert_equals(mutationRecords.length == 1,
+ expectedRes,
+ "Should have a mutation exactly when replace() returns true");
+ } else {
+ // For other functions, would need to check when exactly
+ // mutations are supposed to happen.
+ }
+ }
+ // BEGIN LOCAL MODIFICATION
+ e.removeEventListener("DOMAttrModified", onAttrModified);
+ // END LOCAL MODIFICATION
+ if (!shouldThrow) {
+ assert_equals(res, expectedRes, "wrong return value");
+ }
+
+ var expectedAfter = after;
+
+ assert_equals(e.getAttribute("class"), expectedAfter,
+ "wrong class after modification");
+ // BEGIN LOCAL MODIFICATION
+ var expectedMutation = before != after;
+ assert_equals(gMutationEvents.length, expectedMutation ? 1 : 0,
+ "unexpected mutation event count");
+ if (expectedMutation && gMutationEvents.length) {
+ assert_equals(gMutationEvents[0].attrChange,
+ before == null ? MutationEvent.ADDITION
+ : MutationEvent.MODIFICATION,
+ "wrong type of attribute change");
+ // If there wasn't any previous attribute, prevValue will return an empty
+ // string.
+ var expectedPrevValue = before === null ? "" : before;
+ assert_equals(gMutationEvents[0].prevValue, expectedPrevValue,
+ "wrong previous value");
+ assert_equals(gMutationEvents[0].newValue, after, "wrong new value");
+ }
+ // END LOCAL MODIFICATION
+ }, "classList." + funcName + "(" + args.map(format_value).join(", ") +
+ ") with attribute value " + format_value(before) + desc);
+}
+
+function assignToClassListStrict(e) {
+ "use strict";
+ e.classList = "foo";
+ e.removeAttribute("class");
+}
+
+function assignToClassList(e) {
+ var expect = e.classList;
+ e.classList = "foo";
+ assert_equals(e.classList, expect,
+ "classList should be unchanged after assignment");
+ e.removeAttribute("class");
+}
+
+function testClassList(e, desc) {
+
+ // assignment
+
+ test(function() {
+ assignToClassListStrict(e);
+ assignToClassList(e);
+ }, "Assigning to classList" + desc);
+
+ // supports
+ test(function() {
+ assert_throws_js(TypeError, function() {
+ e.classList.supports("a");
+ })
+ }, ".supports() must throw TypeError" + desc);
+
+ // length attribute
+
+ function checkLength(value, length) {
+ test(function() {
+ setClass(e, value);
+ assert_equals(e.classList.length, length);
+ }, "classList.length when " +
+ (value === null ? "removed" : "set to " + format_value(value)) + desc);
+ }
+
+ checkLength(null, 0);
+ checkLength("", 0);
+ checkLength(" \t \f", 0);
+ checkLength("a", 1);
+ checkLength("a A", 2);
+ checkLength("\r\na\t\f", 1);
+ checkLength("a a", 1);
+ checkLength("a a a a a a", 1);
+ checkLength("a a b b", 2);
+ checkLength("a A B b", 4);
+ checkLength("a b c c b a a b c c", 3);
+ checkLength(" a a b", 2);
+ checkLength("a\tb\nc\fd\re f", 6);
+
+ // [Stringifies]
+
+ function checkStringifier(value, expected) {
+ test(function() {
+ setClass(e, value);
+ assert_equals(e.classList.toString(), expected);
+ }, "classList.toString() when " +
+ (value === null ? "removed" : "set to " + format_value(value)) + desc);
+ }
+
+ checkStringifier(null, "");
+ checkStringifier("foo", "foo");
+ checkStringifier(" a a b", " a a b");
+
+ // item() method
+
+ function checkItems(attributeValue, expectedValues) {
+ function checkItemFunction(index, expected) {
+ assert_equals(e.classList.item(index), expected,
+ "classList.item(" + index + ")");
+ }
+
+ function checkItemArray(index, expected) {
+ assert_equals(e.classList[index], expected, "classList[" + index + "]");
+ }
+
+ test(function() {
+ setClass(e, attributeValue);
+
+ checkItemFunction(-1, null);
+ checkItemArray(-1, undefined);
+
+ var i = 0;
+ while (i < expectedValues.length) {
+ checkItemFunction(i, expectedValues[i]);
+ checkItemArray(i, expectedValues[i]);
+ i++;
+ }
+
+ checkItemFunction(i, null);
+ checkItemArray(i, undefined);
+
+ checkItemFunction(0xffffffff, null);
+ checkItemArray(0xffffffff, undefined);
+
+ checkItemFunction(0xfffffffe, null);
+ checkItemArray(0xfffffffe, undefined);
+ }, "classList.item() when set to " + format_value(attributeValue) + desc);
+ }
+
+ checkItems(null, []);
+ checkItems("a", ["a"]);
+ checkItems("aa AA aa", ["aa", "AA"]);
+ checkItems("a b", ["a", "b"]);
+ checkItems(" a a b", ["a", "b"]);
+ checkItems("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"]);
+
+ // contains() method
+
+ function checkContains(attributeValue, args, expectedRes) {
+ if (!Array.isArray(expectedRes)) {
+ expectedRes = Array(args.length).fill(expectedRes);
+ }
+ setClass(e, attributeValue);
+ for (var i = 0; i < args.length; i++) {
+ test(function() {
+ assert_equals(e.classList.contains(args[i]), expectedRes[i],
+ "classList.contains(\"" + args[i] + "\")");
+ }, "classList.contains(" + format_value(args[i]) + ") when set to " +
+ format_value(attributeValue) + desc);
+ }
+ }
+
+ checkContains(null, ["a", "", " "], false);
+ checkContains("", ["a"], false);
+
+ checkContains("a", ["a"], true);
+ checkContains("a", ["aa", "b", "A", "a.", "a)",, "a'", 'a"', "a$", "a~",
+ "a?", "a\\"], false);
+
+ // All "ASCII whitespace" per spec, before and after
+ checkContains("a", ["a\t", "\ta", "a\n", "\na", "a\f", "\fa", "a\r", "\ra",
+ "a ", " a"], false);
+
+ checkContains("aa AA", ["aa", "AA", "aA"], [true, true, false]);
+ checkContains("a a a", ["a", "aa", "b"], [true, false, false]);
+ checkContains("a b c", ["a", "b"], true);
+
+ checkContains("null undefined", [null, undefined], true);
+ checkContains("\t\n\f\r a\t\n\f\r b\t\n\f\r ", ["a", "b"], true);
+
+ // add() method
+
+ function checkAdd(before, argument, after, expectedException) {
+ checkModification(e, "add", argument, undefined, before, after,
+ expectedException, desc);
+ // Also check force toggle
+ // XXX https://github.com/whatwg/dom/issues/443
+ //if (!Array.isArray(argument)) {
+ // checkModification(e, "toggle", [argument, true], true, before, after,
+ // expectedException);
+ //}
+ }
+
+ checkAdd(null, "", null, "SyntaxError");
+ checkAdd(null, ["a", ""], null, "SyntaxError");
+ checkAdd(null, " ", null, "InvalidCharacterError");
+ checkAdd(null, "\ta", null, "InvalidCharacterError");
+ checkAdd(null, "a\t", null, "InvalidCharacterError");
+ checkAdd(null, "\na", null, "InvalidCharacterError");
+ checkAdd(null, "a\n", null, "InvalidCharacterError");
+ checkAdd(null, "\fa", null, "InvalidCharacterError");
+ checkAdd(null, "a\f", null, "InvalidCharacterError");
+ checkAdd(null, "\ra", null, "InvalidCharacterError");
+ checkAdd(null, "a\r", null, "InvalidCharacterError");
+ checkAdd(null, " a", null, "InvalidCharacterError");
+ checkAdd(null, "a ", null, "InvalidCharacterError");
+ checkAdd(null, ["a", " "], null, "InvalidCharacterError");
+ checkAdd(null, ["a", "aa "], null, "InvalidCharacterError");
+
+ checkAdd("a", "a", "a");
+ checkAdd("aa", "AA", "aa AA");
+ checkAdd("a b c", "a", "a b c");
+ checkAdd("a a a b", "a", "a b");
+ checkAdd(null, "a", "a");
+ checkAdd("", "a", "a");
+ checkAdd(" ", "a", "a");
+ checkAdd(" \f", "a", "a");
+ checkAdd("a", "b", "a b");
+ checkAdd("a b c", "d", "a b c d");
+ checkAdd("a b c ", "d", "a b c d");
+ checkAdd(" a a b", "c", "a b c");
+ checkAdd(" a a b", "a", "a b");
+ checkAdd("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", "a b c");
+
+ // multiple add
+ checkAdd("a b c ", ["d", "e"], "a b c d e");
+ checkAdd("a b c ", ["a", "a"], "a b c");
+ checkAdd("a b c ", ["d", "d"], "a b c d");
+ checkAdd("a b c a ", [], "a b c");
+ checkAdd(null, ["a", "b"], "a b");
+ checkAdd("", ["a", "b"], "a b");
+
+ checkAdd(null, null, "null");
+ checkAdd(null, undefined, "undefined");
+
+ // remove() method
+
+ function checkRemove(before, argument, after, expectedException) {
+ checkModification(e, "remove", argument, undefined, before, after,
+ expectedException, desc);
+ // Also check force toggle
+ // XXX https://github.com/whatwg/dom/issues/443
+ //if (!Array.isArray(argument)) {
+ // checkModification(e, "toggle", [argument, false], false, before, after,
+ // expectedException);
+ //}
+ }
+
+ checkRemove(null, "", null, "SyntaxError");
+ checkRemove(null, " ", null, "InvalidCharacterError");
+ checkRemove("\ta", "\ta", "\ta", "InvalidCharacterError");
+ checkRemove("a\t", "a\t", "a\t", "InvalidCharacterError");
+ checkRemove("\na", "\na", "\na", "InvalidCharacterError");
+ checkRemove("a\n", "a\n", "a\n", "InvalidCharacterError");
+ checkRemove("\fa", "\fa", "\fa", "InvalidCharacterError");
+ checkRemove("a\f", "a\f", "a\f", "InvalidCharacterError");
+ checkRemove("\ra", "\ra", "\ra", "InvalidCharacterError");
+ checkRemove("a\r", "a\r", "a\r", "InvalidCharacterError");
+ checkRemove(" a", " a", " a", "InvalidCharacterError");
+ checkRemove("a ", "a ", "a ", "InvalidCharacterError");
+ checkRemove("aa ", "aa ", null, "InvalidCharacterError");
+
+ checkRemove(null, "a", null);
+ checkRemove("", "a", "");
+ checkRemove("a b c", "d", "a b c");
+ checkRemove("a b c", "A", "a b c");
+ checkRemove(" a a a ", "a", "");
+ checkRemove("a b", "a", "b");
+ checkRemove("a b ", "a", "b");
+ checkRemove("a a b", "a", "b");
+ checkRemove("aa aa bb", "aa", "bb");
+ checkRemove("a a b a a c a a", "a", "b c");
+
+ checkRemove("a b c", "b", "a c");
+ checkRemove("aaa bbb ccc", "bbb", "aaa ccc");
+ checkRemove(" a b c ", "b", "a c");
+ checkRemove("a b b b c", "b", "a c");
+
+ checkRemove("a b c", "c", "a b");
+ checkRemove(" a b c ", "c", "a b");
+ checkRemove("a b c c c", "c", "a b");
+
+ checkRemove("a b a c a d a", "a", "b c d");
+ checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd");
+
+ checkRemove("\ra\na\ta\f", "a", "");
+ checkRemove("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "b");
+
+ // multiple remove
+ checkRemove("a b c ", ["d", "e"], "a b c");
+ checkRemove("a b c ", ["a", "b"], "c");
+ checkRemove("a b c ", ["a", "c"], "b");
+ checkRemove("a b c ", ["a", "a"], "b c");
+ checkRemove("a b c ", ["d", "d"], "a b c");
+ checkRemove("a b c ", [], "a b c");
+ checkRemove(null, ["a", "b"], null);
+ checkRemove("", ["a", "b"], "");
+ checkRemove("a a", [], "a");
+
+ checkRemove("null", null, "");
+ checkRemove("undefined", undefined, "");
+
+ // toggle() method
+
+ function checkToggle(before, argument, expectedRes, after, expectedException) {
+ checkModification(e, "toggle", argument, expectedRes, before, after,
+ expectedException, desc);
+ }
+
+ checkToggle(null, "", null, null, "SyntaxError");
+ checkToggle(null, "aa ", null, null, "InvalidCharacterError");
+
+ checkToggle(null, "a", true, "a");
+ checkToggle("", "a", true, "a");
+ checkToggle(" ", "a", true, "a");
+ checkToggle(" \f", "a", true, "a");
+ checkToggle("a", "b", true, "a b");
+ checkToggle("a", "A", true, "a A");
+ checkToggle("a b c", "d", true, "a b c d");
+ checkToggle(" a a b", "d", true, "a b d");
+
+ checkToggle("a", "a", false, "");
+ checkToggle(" a a a ", "a", false, "");
+ checkToggle(" A A A ", "a", true, "A a");
+ checkToggle(" a b c ", "b", false, "a c");
+ checkToggle(" a b c b b", "b", false, "a c");
+ checkToggle(" a b c ", "c", false, "a b");
+ checkToggle(" a b c ", "a", false, "b c");
+ checkToggle(" a a b", "b", false, "a");
+ checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", false, "b");
+ checkToggle("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "c", true, "a b c");
+
+ checkToggle("null", null, false, "");
+ checkToggle("", null, true, "null");
+ checkToggle("undefined", undefined, false, "");
+ checkToggle("", undefined, true, "undefined");
+
+
+ // tests for the force argument handling
+ // XXX Remove these if https://github.com/whatwg/dom/issues/443 is fixed
+
+ function checkForceToggle(before, argument, force, expectedRes, after, expectedException) {
+ checkModification(e, "toggle", [argument, force], expectedRes, before,
+ after, expectedException, desc);
+ }
+
+ checkForceToggle("", "a", true, true, "a");
+ checkForceToggle("a", "a", true, true, "a");
+ checkForceToggle("a", "b", true, true, "a b");
+ checkForceToggle("a b", "b", true, true, "a b");
+ checkForceToggle("", "a", false, false, "");
+ checkForceToggle("a", "a", false, false, "");
+ checkForceToggle("a", "b", false, false, "a");
+ checkForceToggle("a b", "b", false, false, "a");
+
+
+ // replace() method
+ function checkReplace(before, token, newToken, expectedRes, after, expectedException) {
+ checkModification(e, "replace", [token, newToken], expectedRes, before,
+ after, expectedException, desc);
+ }
+
+ checkReplace(null, "", "a", null, null, "SyntaxError");
+ checkReplace(null, "", " ", null, null, "SyntaxError");
+ checkReplace(null, " ", "a", null, null, "InvalidCharacterError");
+ checkReplace(null, "\ta", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\t", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "\na", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\n", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "\fa", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\f", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "\ra", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a\r", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, " a", "b", null, null, "InvalidCharacterError");
+ checkReplace(null, "a ", "b", null, null, "InvalidCharacterError");
+
+ checkReplace(null, "a", "", null, null, "SyntaxError");
+ checkReplace(null, " ", "", null, null, "SyntaxError");
+ checkReplace(null, "a", " ", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\ta", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\t", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\na", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\n", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\fa", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\f", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "\ra", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a\r", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", " a", null, null, "InvalidCharacterError");
+ checkReplace(null, "b", "a ", null, null, "InvalidCharacterError");
+
+ checkReplace("a", "a", "a", true, "a");
+ checkReplace("a", "a", "b", true, "b");
+ checkReplace("a", "A", "b", false, "a");
+ checkReplace("a b", "b", "A", true, "a A");
+ checkReplace("a b", "c", "a", false, "a b");
+ checkReplace("a b c", "d", "e", false, "a b c");
+ // https://github.com/whatwg/dom/issues/443
+ checkReplace("a a a b", "a", "a", true, "a b");
+ checkReplace("a a a b", "c", "d", false, "a a a b");
+ checkReplace(null, "a", "b", false, null);
+ checkReplace("", "a", "b", false, "");
+ checkReplace(" ", "a", "b", false, " ");
+ checkReplace(" a \f", "a", "b", true, "b");
+ checkReplace("a b c", "b", "d", true, "a d c");
+ checkReplace("a b c", "c", "a", true, "a b");
+ checkReplace("c b a", "c", "a", true, "a b");
+ checkReplace("a b a", "a", "c", true, "c b");
+ checkReplace("a b a", "b", "c", true, "a c");
+ checkReplace(" a a b", "a", "c", true, "c b");
+ checkReplace(" a a b", "b", "c", true, "a c");
+ checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "a", "c", true, "c b");
+ checkReplace("\t\n\f\r a\t\n\f\r b\t\n\f\r ", "b", "c", true, "a c");
+
+ checkReplace("a null", null, "b", true, "a b");
+ checkReplace("a b", "a", null, true, "null b");
+ checkReplace("a undefined", undefined, "b", true, "a b");
+ checkReplace("a b", "a", undefined, true, "undefined b");
+}
+
+var content = document.getElementById("content");
+
+var htmlNode = document.createElement("div");
+content.appendChild(htmlNode);
+testClassList(htmlNode, " (HTML node)");
+
+var xhtmlNode = document.createElementNS(XHTML_NS, "div");
+content.appendChild(xhtmlNode);
+testClassList(xhtmlNode, " (XHTML node)");
+
+var mathMLNode = document.createElementNS(MATHML_NS, "math");
+content.appendChild(mathMLNode);
+testClassList(mathMLNode, " (MathML node)");
+
+var xmlNode = document.createElementNS(null, "foo");
+content.appendChild(xmlNode);
+testClassList(xmlNode, " (XML node with null namespace)");
+
+var fooNode = document.createElementNS("http://example.org/foo", "foo");
+content.appendChild(fooNode);
+testClassList(fooNode, " (foo node)");
+</script>
diff --git a/testing/web-platform/mozilla/tests/dom/delayed_window_print.html b/testing/web-platform/mozilla/tests/dom/delayed_window_print.html
new file mode 100644
index 0000000000..0bb9977184
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/delayed_window_print.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for delaying window.print() before load</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+let t = async_test("Delayed print before load");
+let beforePrintCalled = false;
+window.addEventListener("beforeprint", t.step_func(function() {
+ assert_false(beforePrintCalled, "Should only call beforeprint once");
+ beforePrintCalled = true;
+ assert_true(
+ !!document.getElementById("before-load"),
+ "Should show contents that get added before load"
+ );
+ assert_true(
+ !!document.getElementById("during-load"),
+ "Should show contents that get added during load"
+ );
+ setTimeout(function() { t.done(); }, 0);
+}));
+
+t.step(function() {
+ window.print();
+
+ let div = document.createElement("div");
+ div.id = "before-load";
+ document.body.appendChild(div);
+});
+
+window.addEventListener("load", t.step_func(function() {
+ window.print();
+
+ let div = document.createElement("div");
+ div.id = "during-load";
+ document.body.appendChild(div);
+}));
+</script>
diff --git a/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html b/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html
new file mode 100644
index 0000000000..1fb70aa5b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/dispatch_select_event.html
@@ -0,0 +1,35 @@
+<!-- See also Bug 1679427.
+Ensure `select` event is only fired once when tab-ing to an `<input>` element.
+-->
+<!doctype html>
+<html>
+<head>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+ <button>Press this button and Press Tab</button><input value="abc">
+ <script>
+ promise_test(async t => {
+ await new Promise(resolve => { window.onload = resolve; });
+ const button = document.querySelector("button");
+ const input = document.querySelector("input");
+
+ let countSelectEvent = 0;
+ input.addEventListener("select", event => {
+ countSelectEvent++;
+ });
+
+ button.focus();
+ const tabKey = "\uE004";
+ await test_driver.send_keys(button, tabKey);
+ await new Promise(resolve => requestAnimationFrame(
+ () => requestAnimationFrame(resolve)
+ ));
+ assert_equals(countSelectEvent, 1, "Select event was fired more than once!");
+ }, "Select event should only be fired once.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html b/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html
new file mode 100644
index 0000000000..5de81c866f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/focus-invalid-uri-link.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script>
+function abs(uri) {
+ return new URL(uri, window.location.href).href;
+}
+const CHILD_DOC = `
+<!doctype html>
+<script src="${abs("/resources/testdriver.js")}"></` + `script>
+<script src="${abs("/resources/testdriver-actions.js")}"></` + `script>
+<script src="${abs("/resources/testdriver-vendor.js")}"></` + `script>
+<script>
+ test_driver.set_test_context(opener);
+</` + `script>
+
+<a href>To link or not to link</a>
+
+<script>
+onload = async function() {
+ let link = document.querySelector("a");
+ link.focus();
+ let focused = document.activeElement == link;
+ let clicked = new Promise(resolve => {
+ link.addEventListener("click", resolve, { once: true });
+ });
+ const enterKey = '\\uE007';
+ await test_driver.send_keys(link, enterKey);
+ await clicked;
+ test_driver.message_test({ testResult: true, focused, clicked: true });
+};
+</` + `script>
+`
+
+promise_test(async function(t) {
+ await new Promise(resolve => {
+ window.onload = resolve;
+ })
+
+ let messagePromise = new Promise(resolve => {
+ addEventListener("message", function(msg) {
+ if (msg.data.testResult) {
+ resolve(msg);
+ }
+ });
+ });
+
+ let win = window.open("data:text/html," + escape(CHILD_DOC));
+
+ assert_true(true, "Window opened");
+ let message = await messagePromise;
+ assert_true(true, "message: " + JSON.stringify(message));
+
+ let { focused, clicked } = message.data;
+ assert_true(focused, "Link should be focusable");
+ assert_true(clicked, "Link should be keyboard activatable");
+
+ win.close();
+}, "Link to invalid URI should be focusable and keyboard activatable");
+</script>
diff --git a/testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js b/testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js
new file mode 100644
index 0000000000..6e8192aaef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/fs/fs-writable_unlocked_on_tab_close.https.window.js
@@ -0,0 +1,70 @@
+// META: title=Origin private file system used from multiple tabs
+// META: script=support/testHelpers.js
+
+promise_test(async t => {
+ const bc = new BroadcastChannel("Coordinate writables");
+
+ function firstReady(win) {
+ return new Promise(resolve => {
+ // Blur triggers after the unload event and after window.closed is set
+ win.onblur = () => {
+ // Closing the low-level file handle may get stuck, but the unlocking
+ // request can only be sent to the parent after the handle is closed.
+ // There is no guarantee when or if the closing will be complete.
+ //
+ // Therefore, the content process shutdown does not wait for the
+ // completion but sets window.closed and calls the unload listeners
+ // while actually still holding onto some resources.
+ //
+ // Since in this test we mainly want to ensure that a file
+ // does not remain locked indefinitely, we wait for a reasonable amount
+ // of time before creating a new writable, corresponding roughly to
+ // a 500ms closing delay.
+ const timeoutMs = 400;
+ setTimeout(() => {
+ resolve(win);
+ }, timeoutMs);
+ };
+ });
+ }
+
+ const firstTabName = "support/fs-open_writable_then_close_tab.sub.html";
+ const firstTab = await firstReady(window.open(firstTabName));
+ assert_true(firstTab.closed, "Is the first tab already closed?");
+
+ function secondReady(win) {
+ return new Promise(resolve => {
+ bc.onmessage = e => {
+ if (e.data === "Second window ready!") {
+ resolve(win);
+ }
+ };
+ });
+ }
+
+ const secondTabName = "support/fs-open_writable_after_trigger.sub.html";
+ const secondTab = await secondReady(window.open(secondTabName));
+
+ let isDone = false;
+ let childStatus = "Success";
+
+ const secondSucceeded = new Promise(resolve => {
+ bc.onmessage = e => {
+ isDone = true;
+ if (e.data !== "Success") {
+ childStatus = e.data;
+ }
+
+ resolve();
+ };
+ });
+
+ bc.postMessage("Create writable in the second window");
+
+ await secondSucceeded;
+ assert_true(isDone, "Did the second tab respond?");
+
+ await fetch_tests_from_window(secondTab);
+
+ assert_equals(childStatus, "Success");
+}, `writable available for other tabs after one tab is closed`);
diff --git a/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html
new file mode 100644
index 0000000000..b64d97083f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_after_trigger.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<title>Child context test(s)</title>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="testHelpers.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <script>
+ const channel = new BroadcastChannel("Coordinate writables");
+
+ let triggered = false;
+
+ channel.onmessage = e => {
+ if ("Create writable in the second window" === e.data) {
+ triggered = true;
+ }
+ };
+
+ channel.postMessage("Second window ready!");
+
+ promise_test(async t => {
+ try {
+ const maxWaitMs = 2000;
+ await waitUntil(() => { return triggered; }, maxWaitMs);
+ assert_true(triggered, "Did we receive a trigger?");
+
+ const dir = await navigator.storage.getDirectory();
+ const opts = { create: true };
+ const file = await dir.getFileHandle('funky-file-handle', opts);
+ let writable = await file.createWritable({});
+ t.add_cleanup(async () => { await writable.close(); });
+ assert_true(!!writable, "Did we receive a writable?");
+
+ channel.postMessage("Success");
+ } catch(err) {
+ channel.postMessage(err.message);
+
+ throw err;
+ }
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html
new file mode 100644
index 0000000000..098fe83b1f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/fs/support/fs-open_writable_then_close_tab.sub.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<title>Child context test(s)</title>
+<head>
+ <script src="/resources/testharness.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <script>
+ window.addEventListener('load', async () => {
+ const dir = await navigator.storage.getDirectory();
+ const opts = { create: true };
+ const file = await dir.getFileHandle('funky-file-handle', opts);
+ const writable = await file.createWritable({});
+
+ window.close();
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js b/testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js
new file mode 100644
index 0000000000..5cf47435c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/fs/support/testHelpers.js
@@ -0,0 +1,15 @@
+async function waitUntil(isWaitDone, untilMs, stepMs = 25) {
+ const startMs = Date.now();
+
+ return new Promise((resolve, reject) => {
+ const areWeDoneYet = setInterval(async function() {
+ if (await isWaitDone()) {
+ clearInterval(areWeDoneYet);
+ resolve();
+ } else if (Date.now() > startMs + untilMs) {
+ clearInterval(areWeDoneYet);
+ reject(new Error("Timed out after " + untilMs + "ms"));
+ }
+ }, stepMs);
+ });
+}
diff --git a/testing/web-platform/mozilla/tests/dom/range-in-two-selections.html b/testing/web-platform/mozilla/tests/dom/range-in-two-selections.html
new file mode 100644
index 0000000000..a37464d55a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/range-in-two-selections.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+</head>
+
+<body>
+<span>One two</span>
+<script>
+promise_test(async function (t) {
+ await new Promise(resolve => {
+ window.onload = resolve;
+ })
+ const range = document.createRange();
+ range.setStart(document.body, 0);
+ range.setEnd(document.body, 1);
+ const highlight = new Highlight(range);
+ CSS.highlights.set("foo", highlight);
+ document.getSelection().addRange(range);
+
+ const highlightRange = highlight.entries().next().value[0];
+ const selectionRange = document.getSelection().getRangeAt(0);
+ assert_equals(
+ highlightRange,
+ selectionRange,
+ "The same range must be present in the highlight and the Selection."
+ );
+}, "Range is shared between a custom highlight and the document's Selection.");
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html b/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html
new file mode 100644
index 0000000000..7eb9dd1e40
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/test.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="throttling.js"></script>
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js b/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js
new file mode 100644
index 0000000000..280b0adc0d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/throttling.js
@@ -0,0 +1,136 @@
+function waitForLoad() {
+ return new Promise(resolve => addEventListener('load', resolve))
+ .then(() => delay(10));
+}
+
+function delay(timeout) {
+ return new Promise(resolve => step_timeout(() => resolve(), 10));
+}
+
+function busy(work) {
+ return delay(10).then(() => new Promise(resolve => {
+ step_timeout(() => {
+ let end = performance.now() + work;
+ while (performance.now() < end) {
+
+ }
+
+ resolve();
+ }, 1);
+ }));
+}
+
+function getThrottlingRate(delay) {
+ return new Promise(resolve => {
+ let start = performance.now();
+ setTimeout(() => {
+ let rate = Math.floor((performance.now() - start) / delay);
+ resolve(rate);
+ }, delay);
+ });
+}
+
+function addElement(t, element, src) {
+ return new Promise((resolve, reject) => {
+ let e = document.createElement(element);
+ e.addEventListener('load', () => resolve(e));
+ if (src) {
+ e.src = src;
+ }
+ document.body.appendChild(e);
+ t.add_cleanup(() => e.remove());
+ });
+}
+
+function inFrame(t) {
+ return addElement(t, "iframe", "resources/test.html")
+ .then(frame => delay(10).then(() => Promise.resolve(frame.contentWindow)));
+}
+
+function addWebSocket(t, url) {
+ return new Promise((resolve, reject) => {
+ let socket = new WebSocket(url);
+ socket.onopen = () => {
+ t.add_cleanup(() => socket.close());
+ resolve();
+ };
+ socket.onerror = reject;
+ });
+}
+
+function addRTCPeerConnection(t) {
+ return new Promise((resolve, reject) => {
+ let connection = new RTCPeerConnection();
+ t.add_cleanup(() => {
+ connection.close()
+ });
+
+ resolve();
+ });
+}
+
+function addIndexedDB(t) {
+ return new Promise((resolve, reject) => {
+ let iDBState = {
+ running: false,
+ db: null
+ };
+
+ let req = indexedDB.open("testDB", 1);
+
+ req.onupgradeneeded = e => {
+ let db = e.target.result;
+ let store = db.createObjectStore("testOS", {keyPath: "id"});
+ let index = store.createIndex("index", ["col"]);
+ };
+
+ req.onsuccess = e => {
+ let db = iDBState.db = e.target.result;
+ let store = db.transaction("testOS", "readwrite").objectStore("testOS");
+ let ctr = 0;
+
+ iDBState.running = true;
+
+ function putLoop() {
+ if (!iDBState.running) {
+ return;
+ }
+
+ let req = store.put({id: ctr++, col: "foo"});
+ req.onsuccess = putLoop;
+
+ if (!iDBState.request) {
+ iDBState.request = req;
+ }
+ }
+
+ putLoop();
+ resolve();
+ };
+
+ t.add_cleanup(() => {
+ iDBState.running = false;
+ iDBState.db && iDBState.db.close();
+ iDBState.db = null;
+ });
+ });
+}
+
+function addWebAudio(t) {
+ return new Promise(resolve => {
+ let context = new (window.AudioContext || window.webkitAudioContext)();
+ context.onstatechange = () => (context.state === "running") && resolve();
+
+ let gain = context.createGain();
+ gain.gain.value = 0.1;
+ gain.connect(context.destination);
+
+ let webaudionode = context.createOscillator();
+ webaudionode.type = 'square';
+ webaudionode.frequency.value = 440; // value in hertz
+ webaudionode.connect(gain);
+ webaudionode.start();
+
+ t.add_cleanup(() => webaudionode.stop());
+ });
+}
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js b/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js
new file mode 100644
index 0000000000..a1ac273a54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/resources/ws.sub.js
@@ -0,0 +1,3 @@
+var __SERVER__NAME = "{{host}}";
+var __PORT = "{{ports[ws][0]}}";
+var __PATH = "echo";
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js
new file mode 100644
index 0000000000..86cefc8a81
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-1.window.js
@@ -0,0 +1,10 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => busy(100)
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_greater_than(rate, 10, "Timeout wasn't throttled");
+ }), "Throttle when all budget has been used.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js
new file mode 100644
index 0000000000..3ccb35dc08
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-2.window.js
@@ -0,0 +1,11 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => inFrame(t)
+ .then(win => win.busy(100)
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_greater_than(rate, 10, "Timeout wasn't throttled");
+ }), "Throttle iframe when all budget has been used");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js
new file mode 100644
index 0000000000..d1c38bcc12
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-3.window.js
@@ -0,0 +1,11 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => inFrame(t)
+ .then(win => busy(100)
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when all budget in parent has been used");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js
new file mode 100644
index 0000000000..b072b51809
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-4.window.js
@@ -0,0 +1,11 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => inFrame(t)
+ .then(win => win.busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle parent when all budget in iframe has been used");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js
new file mode 100644
index 0000000000..73c734a584
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-indexeddb.window.js
@@ -0,0 +1,35 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addIndexedDB(t)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open IndexedDB transactions.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addIndexedDB(t))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open IndexedDB transactions in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addIndexedDB(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open IndexedDB transactions in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addIndexedDB(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open IndexedDB transactions in iframe.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js
new file mode 100644
index 0000000000..5cd7193788
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webaudio.window.js
@@ -0,0 +1,35 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addWebAudio(t)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there is active WebAudio.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebAudio(t))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there is active WebAudio in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addWebAudio(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there is active WebAudio in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebAudio(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there is active WebAudio in iframe.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js
new file mode 100644
index 0000000000..2842f77e44
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-webrtc.window.js
@@ -0,0 +1,35 @@
+// META: script=resources/throttling.js
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addRTCPeerConnection(t)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open RTCPeerConnections.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addRTCPeerConnection(t))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open RTCPeerConnections in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addRTCPeerConnection(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open RTCPeerConnections in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addRTCPeerConnection(t)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open RTCPeerConnections in iframe.");
diff --git a/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js b/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js
new file mode 100644
index 0000000000..185654e04d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/dom/throttling/throttling-ws.window.js
@@ -0,0 +1,37 @@
+// META: script=resources/ws.sub.js
+// META: script=resources/throttling.js
+let server = "ws://" + __SERVER__NAME + ":" + __PORT + "/" + __PATH;
+
+setup(() => waitForLoad()
+ .then(() => "setup done"));
+
+promise_test(t => addWebSocket(t, server)
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open WebSockets.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebSocket(t, server))
+ .then(() => busy(100))
+ .then(() => getThrottlingRate(100))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle when there are open WebSockets in iframe.");
+
+promise_test(t => inFrame(t)
+ .then(win => addWebSocket(t, server)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open WebSockets in parent.");
+
+promise_test(t => inFrame(t)
+ .then(win => win.addWebSocket(t, server)
+ .then(() => win.busy(100))
+ .then(() => win.getThrottlingRate(100)))
+ .then(rate => {
+ assert_less_than(rate, 10, "Timeout was throttled");
+ }), "Don't throttle iframe when there are open WebSockets in iframe.");
diff --git a/testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html b/testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html
new file mode 100644
index 0000000000..6c065cbc12
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/editor/delete-space-after-double-click-selection.html
@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <title>Test for Bug 1783641</title>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <style>
+ .testStyle {
+ font-family: 'Courier New', Courier, monospace;
+ font-size: 12px;
+ padding: 0px;
+ width: 200px;
+ }
+ </style>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783641">Mozilla Bug 1783641</a><br />
+ <span class="testStyle" id="placeholder"></span>
+ <input class="testStyle" type="text" />
+ <div class="testStyle" contenteditable></div>
+ <textarea class="testStyle"></textarea>
+ <script>
+
+ promise_test(async t => {
+ await new Promise(resolve => { window.onload = resolve; });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["editor.word_select.delete_space_after_doubleclick_selection", true],
+ ["layout.word_select.eat_space_to_next_word", false]
+ ]
+ });
+ }, "Test setup");
+ const placeHolder = document.getElementById("placeholder");
+ const deleteKey = "\uE017";
+
+ function waitForRender() {
+ return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ };
+
+ for (const selector of ["input", "div[contenteditable]", "textarea"]) {
+ const editableElement = document.querySelector(selector);
+ editableElement.focus();
+
+ /**
+ * Helper functions to set or get the value and the current selection of `editableElement`,
+ * regardless of its actual type.
+ */
+ const setValue = (aValue) => {
+ editableElement.tagName.toLowerCase() == "div"
+ ? editableElement.innerHTML = aValue
+ : editableElement.value = aValue;
+ }
+ const getValue = () => {
+ return editableElement.tagName.toLowerCase() == "div"
+ ? editableElement.innerHTML
+ : editableElement.value;
+ };
+ const getSelection = () => {
+ return editableElement.tagName.toLowerCase() == "div"
+ ? document.getSelection().toString()
+ : editableElement.value.substring(
+ editableElement.selectionStart,
+ editableElement.selectionEnd
+ );
+ };
+
+ /**
+ * Places a double click in `editableElement` at exactly the end of `aPlaceHolderText` and press delete.
+ * `aPlaceholderText` therefore should contain the same text as the value of the `editableElement`
+ * up to the point where the doubleclick should happen.
+ *
+ * If `aSelectionValue` is defined, the selection created by the double click is compared to `aSelectionValue`.
+ */
+ const doubleClickAndDelete = async (aPlaceHolderText, aSelectionValue = undefined) => {
+ placeHolder.innerHTML = aPlaceHolderText;
+ editableElement.focus();
+ await waitForRender();
+ const absInputPos = editableElement.getBoundingClientRect();
+ selectionOffset = {
+ x: placeHolder.getBoundingClientRect().width,
+ y: Math.floor(placeHolder.getBoundingClientRect().height / 2)
+ };
+ await (new test_driver.Actions()
+ // for some reason this still doesn't work:
+ // .pointerMove(Math.floor(selectionOffset.x), Math.floor(selectionOffset.y), { origin: editableElement })
+ // but this does:
+ .pointerMove(
+ Math.floor(absInputPos.x + selectionOffset.x),
+ Math.floor(absInputPos.y + selectionOffset.y),
+ { origin: "viewport" }
+ )
+ .pointerDown()
+ .pointerUp()
+ .pointerDown()
+ .pointerUp())
+ .send()
+ await waitForRender();
+ if (aSelectionValue !== undefined) {
+ assert_equals(getSelection(), aSelectionValue, "Wrong selection value!");
+ }
+ return test_driver.send_keys(editableElement, deleteKey);
+ };
+ if (editableElement.tagName.toLowerCase() == "div") {
+ promise_test(async t => {
+ setValue("<p>abc def<span></span></p>");
+ await doubleClickAndDelete("abc de", "def");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "<p>abc</p>",
+ "The <span> at the end of the string must be removed, as well as the whitespace in between words.");
+ }, `${editableElement.tagName}: An empty span at the end of the selection should be considered end of selection!`);
+ }
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("on", "one");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "two",
+ "The whitespace between words must be removed when a word at the beginning is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Remove word at the beginning of string should remove the whitespace in between.`);
+
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one",
+ "The whitespace between words must be removed when a word is selected at the end of the string and deleted!"
+ );
+ }, `${editableElement.tagName}: Remove word at the end of a string should remove the whitespace in between.`);
+
+ promise_test(async t => {
+ setValue("one two three");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one three",
+ "One whitespace between words must be removed when a word is selected and deleted!"
+ );
+ await waitForRender();
+ if (editableElement.tagName.toLowerCase() == "div") {
+ document.getSelection().setBaseAndExtent(
+ editableElement.firstChild,
+ 0,
+ editableElement.firstChild,
+ 3
+ );
+ }
+ else {
+ editableElement.setSelectionRange(0, 3);
+ }
+ await test_driver.send_keys(editableElement, deleteKey);
+ // div[contenteditable] returns '&nbsp;three' here.
+ assert_equals(
+ getValue().replace(/&nbsp;/g, " "),
+ " three",
+ "The whitespace must not be removed when selecting a word without doubleclicking it!"
+ );
+
+ }, `${editableElement.tagName}: Remove word in the middle of a string should remove one whitespace ` +
+ "only if selection is created by double click.");
+
+ promise_test(async t => {
+ setValue("one two three");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one three",
+ "One whitespace between words must be removed when a word is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Only one whitespace character should be removed when there are multiple.`);
+
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one ",
+ "One whitespace character between words must be removed when a word is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Only one whitespace character should be removed when ` +
+ "there are multiple whitespaces and the deleted range is the end of the string.");
+
+ promise_test(async t => {
+ setValue("one two, three");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one, three",
+ "The whitespace in front of the selected word must be removed when punctuation follows selection!"
+ );
+ }, `${editableElement.tagName}: Removing a word before punctuation should remove the whitespace.`);
+
+ promise_test(async t => {
+ setValue("one, two");
+ await doubleClickAndDelete("one, tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one,",
+ "The whitespace in front of the selected word must be removed!"
+ );
+ }, `${editableElement.tagName}: Remove a word after punctuation should remove the whitespace.`);
+
+ promise_test(async t => {
+ setValue("one\u00A0two, three"); // adds a &nbsp;
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one, three",
+ "The whitespace between words must be removed when a word is selected and deleted!"
+ );
+ }, `${editableElement.tagName}: Removing a word between a &nbsp; and punctuation should remove the nbsp character.`);
+
+ if (editableElement.tagName.toLowerCase() == "div") {
+ promise_test(async t => {
+ setValue("one two<br>");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one<br>",
+ "The line break must be preserved!"
+ );
+ }, `${editableElement.tagName}: Removing a word in front of a line break should preserve the line break.`);
+ }
+ if (editableElement.tagName.toLowerCase() == "textarea") {
+ promise_test(async t => {
+ setValue("one two\n");
+ await doubleClickAndDelete("one tw", "two");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "one\n",
+ "The line break must be preserved!"
+ );
+ }, `${editableElement.tagName}: RRemoving a word in front of a line break should preserve the line break.`);
+ }
+ promise_test(async t => {
+ setValue("one two");
+ await doubleClickAndDelete("on", "one");
+ await waitForRender();
+ assert_equals(
+ getValue(),
+ "two",
+ "The whitespace between words must be removed when a word at the beginning is selected and deleted!"
+ );
+ document.execCommand("undo", false, null);
+ assert_equals(
+ getValue(),
+ "one two",
+ "Undo action must restore the original state!"
+ );
+ document.execCommand("redo", false, null);
+ assert_equals(
+ getValue(),
+ "two",
+ "Redo action must remove the word and whitespace again!"
+ );
+ }, `${editableElement.tagName}: Undo and Redo actions should take the removed whitespace into account.`);
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/editor/input-setRangeText-during-noframe-crash.html b/testing/web-platform/mozilla/tests/editor/input-setRangeText-during-noframe-crash.html
new file mode 100644
index 0000000000..815ec994e8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/editor/input-setRangeText-during-noframe-crash.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ const input = document.querySelector("input");
+ input.select();
+ input.type = "text/x-ecmascript";
+ input.setRangeText("foo", 1, 1);
+ document.execCommand("enableObjectResizing");
+});
+</script>
+</head>
+<body>
+<input type="number" value="a">
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html b/testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html
new file mode 100644
index 0000000000..06fe0acf6e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/editor/white-space-handling-in-mail-editor.html
@@ -0,0 +1,371 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<meta name="variant" content="?plaintext=true">
+<meta name="variant" content="?plaintext=false">
+<title>Testing white-space handling in mail editor mode</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div contenteditable></div>
+<script>
+"use strict";
+
+const params = new URLSearchParams(location.search);
+const inPlaintextMode = params.get("plaintext") === "true";
+
+const editingHost = document.querySelector("div[contenteditable]");
+// To show white-spaces as-is, editing host should have "pre-wrap" style.
+// Then, editor does not need to convert white-spaces.
+editingHost.style.whiteSpace = "pre-wrap";
+
+const editor = SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window);
+editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorMailMask;
+if (inPlaintextMode) {
+ editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorPlaintextMask;
+}
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertText", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting first white-space into empty paragraph shouldn't convert the inserting white-space to an NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting second white-space next to a white-space shouldn't convert the inserting white-space nor the existing white-space to NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting 3rd white-space into middle of white-spaces shouldn't convert the inserting white-space nor the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertText", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> a </p>",
+ "<p> a <br></p>",
+ ]
+ );
+}, "Inserting a character into middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> a </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Deleting a character at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Deleting a white-space at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> a </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Forward deleting a character at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Forward deleting at middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertText", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;</p>",
+ "<p>&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting first NBSP into empty paragraph shouldn't convert the inserting NBSP to a white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting second NBSP next to an NBSP shouldn't convert the inserting NBSP nor the existing NBSP to white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertText", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting 3rd NBSP into middle of NBSPs shouldn't convert the inserting NBSP nor the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertText", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting a character into middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0a\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Deleting a character at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 3);
+ document.execCommand("delete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Deleting an NBSP at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0a\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Forward deleting a character at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("forwardDelete");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Forward deleting at middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertHTML", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting first white-space with insertHTML command into empty paragraph shouldn't convert the inserting white-space to an NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting second white-space with insertHTML command next to a white-space shouldn't convert the inserting white-space nor the existing white-space to NBSP");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, " ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> </p>",
+ "<p> <br></p>",
+ ]
+ );
+}, "Inserting 3rd white-space with insertHTML command into middle of white-spaces shouldn't convert the inserting white-space nor the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p> </p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertHTML", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p> a </p>",
+ "<p> a <br></p>",
+ ]
+ );
+}, "Inserting a character with insertHTML command into middle of white-spaces shouldn't convert the existing white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p><br></p>";
+ getSelection().collapse(editingHost.querySelector("p"), 0);
+ document.execCommand("insertHTML", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;</p>",
+ "<p>&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting first NBSP with insertHTML command into empty paragraph shouldn't convert the inserting NBSP to a white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting second NBSP with insertHTML command next to an NBSP shouldn't convert the inserting NBSP nor the existing NBSP to white-space");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 1);
+ document.execCommand("insertHTML", false, "\xA0");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting 3rd NBSP with insertHTML command into middle of NBSPs shouldn't convert the inserting NBSP nor the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>\xA0\xA0\xA0\xA0</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, 2);
+ document.execCommand("insertHTML", false, "a");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;</p>",
+ "<p>&nbsp;&nbsp;a&nbsp;&nbsp;<br></p>",
+ ]
+ );
+}, "Inserting a character with insertHTML command into middle of NBSPs shouldn't convert the existing NBSPs to white-spaces");
+
+test(() => {
+ editingHost.innerHTML = "<p>abc</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, "abc".length);
+ document.execCommand("insertHTML", false, "def ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>abcdef </p>",
+ "<p>abcdef <br></p>",
+ ]
+ );
+}, "Inserting multiple white-spaces with insertHTML command shouldn't convert the white-spaces to NBSPs");
+
+test(() => {
+ editingHost.innerHTML = "<p>abc</p>";
+ getSelection().collapse(editingHost.querySelector("p").firstChild, "abc".length);
+ document.execCommand("insertHTML", false, "def ");
+ assert_in_array(
+ editingHost.innerHTML,
+ [
+ "<p>abcdef </p>",
+ "<p>abcdef <br></p>",
+ ]
+ );
+}, "Inserting multiple NBSPs with insertHTML command shouldn't convert the NBSPs to white-spaces");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js
new file mode 100644
index 0000000000..ad59904fd9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer-mixed-content.js
@@ -0,0 +1,51 @@
+if (this.document === undefined) {
+ importScripts("/common/utils.js");
+ importScripts("/resources/testharness.js");
+ importScripts("/fetch/api/resources/utils.js");
+ importScripts("/common/get-host-info.sub.js");
+}
+
+function testReferrerAfterRedirection(desc, redirectUrl, redirectLocation, referrerPolicy, redirectReferrerPolicy, expectedReferrer) {
+ var url = redirectUrl;
+ var urlParameters = "?location=" + encodeURIComponent(redirectLocation);
+
+ if (redirectReferrerPolicy)
+ urlParameters += "&redirect_referrerpolicy=" + redirectReferrerPolicy;
+
+ var requestInit = {"redirect": "follow", "referrerPolicy": referrerPolicy};
+
+ promise_test(function(test) {
+ return fetch(url + urlParameters, requestInit).then(function(response) {
+ assert_equals(response.status, 200, "Inspect header response's status is 200");
+ assert_equals(response.headers.get("x-request-referer"), expectedReferrer ? expectedReferrer : null, "Check referrer header");
+ });
+ }, desc);
+}
+
+var referrerOrigin = get_host_info().HTTPS_ORIGIN + "/";
+var referrerUrl = location.href;
+
+var RESOURCES_DIR = "/fetch/api/resources/";
+var redirectUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = get_host_info().HTTPS_ORIGIN + RESOURCES_DIR + "inspect-headers.py?headers=referer";
+var httpLocationUrl = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR + "inspect-headers.py?cors&headers=referer";
+
+testReferrerAfterRedirection("Downgrade, empty init, unsafe-url redirect header ", redirectUrl, httpLocationUrl, "", "unsafe-url", referrerUrl);
+testReferrerAfterRedirection("Downgrade, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, httpLocationUrl, "", "no-referrer-when-downgrade", null);
+testReferrerAfterRedirection("Downgrade, empty init, same-origin redirect header ", redirectUrl, httpLocationUrl, "", "same-origin", null);
+testReferrerAfterRedirection("Downgrade, empty init, origin redirect header ", redirectUrl, httpLocationUrl, "", "origin", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty init, origin-when-cross-origin redirect header ", redirectUrl, httpLocationUrl, "", "origin-when-cross-origin", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty init, no-referrer redirect header ", redirectUrl, httpLocationUrl, "", "no-referrer", null);
+testReferrerAfterRedirection("Downgrade, empty init, strict-origin redirect header ", redirectUrl, httpLocationUrl, "", "strict-origin", null);
+testReferrerAfterRedirection("Downgrade, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, httpLocationUrl, "", "strict-origin-when-cross-origin", null);
+
+testReferrerAfterRedirection("Downgrade, empty redirect header, unsafe-url init ", redirectUrl, httpLocationUrl, "unsafe-url", "", referrerUrl);
+testReferrerAfterRedirection("Downgrade, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, httpLocationUrl, "no-referrer-when-downgrade", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, same-origin init ", redirectUrl, httpLocationUrl, "same-origin", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, origin init ", redirectUrl, httpLocationUrl, "origin", "", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty redirect header, origin-when-cross-origin init ", redirectUrl, httpLocationUrl, "origin-when-cross-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Downgrade, empty redirect header, no-referrer init ", redirectUrl, httpLocationUrl, "no-referrer", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, strict-origin init ", redirectUrl, httpLocationUrl, "strict-origin", "", null);
+testReferrerAfterRedirection("Downgrade, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, httpLocationUrl, "strict-origin-when-cross-origin", "", null);
+
+
diff --git a/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html
new file mode 100644
index 0000000000..bcd24892e2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/api/redirect/redirect-referrer.https.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Fetch: redirect referrer handling, mixed content</title>
+ <meta name="author" title="Canon Research France" href="https://www.crf.canon.fr">
+ <meta name="help" href="https://fetch.spec.whatwg.org/#main-fetch">
+ <meta name="help" href="https://fetch.spec.whatwg.org/#http-redirect-fetch">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script src="/common/utils.js"></script>
+ <script src="/fetch/api/resources/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="redirect-referrer-mixed-content.js"></script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority-disabled.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority-disabled.h2.html
new file mode 100644
index 0000000000..8f4a2a15aa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority-disabled.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="timeout" content="long">
+ <title>fetchpriority: internal priorities of corresponding http-on-opening-requests</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="module">
+ import { runTests } from "./fetchpriority.js";
+ runTests({
+ testDataKey: 'kTestDataDisabled',
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.h2.html
new file mode 100644
index 0000000000..43f0b10be2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="timeout" content="long">
+ <title>fetchpriority: internal priorities of corresponding http-on-opening-requests</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script type="module">
+ import { runTests } from "./fetchpriority.js";
+ runTests({
+ testDataKey: 'kTestData',
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.js
new file mode 100644
index 0000000000..dfdfd4a161
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/fetchpriority.js
@@ -0,0 +1,107 @@
+import * as scriptTestsData from "./support/script-tests-data.js";
+import * as linkTestsData from "./support/link-tests-data.js";
+import * as fontfaceTestsData from "./support/font-face-tests-data.js";
+import * as imageTestsData from "./support/image-tests-data.js";
+import * as fetchTestsData from "./support/fetch-tests-data.js";
+
+const kTopicHttpOnOpeningRequest = "http-on-opening-request";
+
+function getFileNameAndSuffixOf(aStr) {
+ return aStr.substr(aStr.lastIndexOf("/") + 1);
+}
+
+let httpOnOpeningRequests = [];
+
+const observeHttpOnOpeningRequest = { observe(aSubject, aTopic) {
+ assert_equals(aTopic, kTopicHttpOnOpeningRequest, "Observed '" +
+ kTopicHttpOnOpeningRequest + "'");
+
+ const fileNameAndSuffix = getFileNameAndSuffixOf(
+ aSubject.QueryInterface(SpecialPowers.Ci.nsIChannel).URI.spec);
+ const internalPriority =
+ aSubject.QueryInterface(SpecialPowers.Ci.nsISupportsPriority).priority;
+
+ httpOnOpeningRequests.push(
+ { fileNameAndSuffix: fileNameAndSuffix,
+ internalPriority: internalPriority});
+}};
+
+SpecialPowers.addObserver(observeHttpOnOpeningRequest,
+ kTopicHttpOnOpeningRequest);
+
+function containsOnlyUniqueFileNames(aRequests) {
+ const fileNames = aRequests.map((r) => r.fileNameAndSuffix);
+ return (new Set(fileNames)).size == fileNames.length;
+}
+
+const kTestGroups = [
+ scriptTestsData, linkTestsData, fontfaceTestsData, imageTestsData, fetchTestsData
+];
+
+const kSupportFolderName = "support";
+
+function runSingleTest(aTestData, aTestFolderName) {
+ promise_test((t) => {
+ return new Promise(resolve => {
+ const testPath = kSupportFolderName + "/" + aTestFolderName + "/" +
+ aTestData.testFileName;
+ var childWindow = window.open(testPath);
+
+ t.add_cleanup(() => {
+ httpOnOpeningRequests = [];
+ childWindow.close();
+ });
+
+ window.addEventListener("message", resolve);
+ }).then(e => {
+ assert_true(typeof e.data === "string", "String message received");
+ assert_equals(e.data, "ChildLoaded", "Child loaded");
+
+ assert_greater_than(aTestData.expectedRequests.length, 0,
+ "Test data should be non-empty");
+
+ assert_true(containsOnlyUniqueFileNames(aTestData.expectedRequests),
+ "Test data contains only unique filenames")
+
+ assert_greater_than(httpOnOpeningRequests.length, 0,
+ "Observed HTTP requests should be non-empty");
+
+ assert_true(containsOnlyUniqueFileNames(httpOnOpeningRequests),
+ "Observed only one HTTP request per filename");
+
+ // The actual order of the "http-on-opening-request"s is not checked,
+ // since the corresponding notification is sent when the resource is
+ // started to be loaded. However, since the resources might be too
+ // quick to load, depending on the machine and network, it can't be
+ // ensured that the server can reflect the priorities correctly.
+ // Hence, here only the internal priority sent to the server is
+ // checked.
+ aTestData.expectedRequests.forEach(
+ (expectedRequest) => {
+ const actualRequest =
+ httpOnOpeningRequests.find(
+ (actualRequest) => actualRequest.fileNameAndSuffix ==
+ expectedRequest.fileNameAndSuffix);
+ assert_not_equals(actualRequest, undefined,
+ "Found request for \"" + expectedRequest.fileNameAndSuffix +
+ "\"");
+ assert_equals(actualRequest.internalPriority,
+ expectedRequest.internalPriority,
+ "Check internal priority for '" +
+ expectedRequest.fileNameAndSuffix + "'");
+ });
+ });
+ }, aTestData.testFileName + ": test different 'fetchpriority' values");
+}
+
+export function runTests(aRunConfig) {
+ for (const testGroup of kTestGroups) {
+ const testDataKey = aRunConfig.testDataKey;
+ if (!testGroup[testDataKey]) {
+ continue;
+ }
+ for (const singleTestData of testGroup[testDataKey]) {
+ runSingleTest(singleTestData, testGroup.kTestFolderName);
+ }
+ }
+}
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests-data.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests-data.js
new file mode 100644
index 0000000000..4edb3ae171
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests-data.js
@@ -0,0 +1,40 @@
+export const kTestFolderName = "fetch-tests";
+
+const kExpectedRequestsOfFetchAPI = [
+ { fileNameAndSuffix: "dummy.css?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?5",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?6",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?7",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?8",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?9",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?10",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ }
+];
+
+export const kTestData = [
+ { testFileName: "fetch-init.h2.html",
+ expectedRequests: kExpectedRequestsOfFetchAPI
+ }
+];
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests/fetch-init.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests/fetch-init.h2.html
new file mode 100644
index 0000000000..0e267b0a3a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/fetch-tests/fetch-init.h2.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ const resource_url = new URL('../resources/dummy.css', location);
+ onload = function() {
+ const kData = [
+ {
+ description: 'fetch() with URL and request_init whose priority is "high" must be fetched with high load priority',
+ request_info: `${resource_url}?1`,
+ request_init: {priority: 'high'}
+ },
+ {
+ description: 'fetch() with URL and request_init whose priority is "auto" must have no effect on resource load priority',
+ request_info: `${resource_url}?2`,
+ request_init: {priority: 'auto'}
+ },
+ {
+ description: 'fetch() with URL and request_init whose priority is missing must have no effect on resource load priority',
+ request_info: `${resource_url}?3`,
+ request_init: {}
+ },
+ {
+ description: 'fetch() with URL and request_init whose priority is "low" must be fetched with low resource load priority',
+ request_info: `${resource_url}?4`,
+ request_init: {priority: 'low'}
+ },
+ {
+ description: 'fetch() with Request whose priority is "low" and request_init whose priority is "high" must have no effect on resource load priority',
+ request_info: new Request(`${resource_url}?5`, {priority: 'low'}),
+ request_init: {priority: 'high'}
+ },
+ {
+ description: 'fetch() with Request whose priority is "high" and request_init whose priority is "low" must be fetched with low resource load priority',
+ request_info: new Request(`${resource_url}?6`, {priority: 'high'}),
+ request_init: {priority: 'low'}
+ },
+ {
+ description: 'fetch() with Request whose priority is "high" and no request_init must be fetched with high resource load priority',
+ request_info: new Request(`${resource_url}?7`, {priority: 'high'})
+ },
+ {
+ description: 'fetch() with Request whose priority is "auto" and no request_init must have no effect on resource load priority',
+ request_info: new Request(`${resource_url}?8`, {priority: 'auto'})
+ },
+ {
+ description: 'fetch() with Request whose priority is missing and no request_init must have no effect on resource load priority',
+ request_info: new Request(`${resource_url}?9`)
+ },
+ {
+ description: 'fetch() with Request whose priority is "low" and no request_init must be fetched with low resource load priority',
+ request_info: new Request(`${resource_url}?10`, {priority: 'low'})
+ }
+ ];
+ for (const data of kData) {
+ const response = fetch(data.request_info, data.request_init);
+ opener.postMessage("ChildLoaded", "*");
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests-data.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests-data.js
new file mode 100644
index 0000000000..513bfe7a03
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests-data.js
@@ -0,0 +1,21 @@
+const kExpectedRequests = [
+ { fileNameAndSuffix: "dummy.font",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+];
+
+export const kTestFolderName = "font-face-tests";
+
+export const kTestData = [
+ { testFileName: "load-font-face-from-head.h2.html",
+ expectedRequests: kExpectedRequests
+ },
+ { testFileName: "load-font-face-from-worker.h2.html",
+ expectedRequests: kExpectedRequests
+ },
+ { testFileName: "load-font-face-from-script.h2.html",
+ expectedRequests: kExpectedRequests
+ },
+];
+
+export const kTestDataDisabled = kTestData;
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/font-face-worker.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/font-face-worker.js
new file mode 100644
index 0000000000..5afd6641b5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/font-face-worker.js
@@ -0,0 +1,5 @@
+const fontFace = new FontFace("dummy-font", "url(../resources/dummy.font)");
+fonts.add(fontFace);
+fontFace.load();
+
+fonts.ready.then(() => { postMessage("Font loaded"); })
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-head.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-head.h2.html
new file mode 100644
index 0000000000..db3ebc148c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-head.h2.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <style>
+ @font-face {
+ font-family: "dummy-font";
+ src: url("../resources/dummy.font");
+ }
+ body {
+ font-family: "dummy-font";
+ }
+ </style>
+</head>
+<body>
+asdf
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-script.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-script.h2.html
new file mode 100644
index 0000000000..90af7200d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-script.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+asdf
+<script>
+ onload = function() {
+ const fontFace = new FontFace("dummy-font", "url(../resources/dummy.font)");
+ document.fonts.add(fontFace);
+ fontFace.load();
+ document.fonts.ready.then(() => opener.postMessage("ChildLoaded", "*"));
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-worker.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-worker.h2.html
new file mode 100644
index 0000000000..795e517594
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/font-face-tests/load-font-face-from-worker.h2.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const worker = new Worker("font-face-worker.js");
+ worker.addEventListener("message", (m) => {
+ if (m.data == "Font loaded") {
+ opener.postMessage("ChildLoaded", "*");
+ }
+ });
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests-data.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests-data.js
new file mode 100644
index 0000000000..ee070c338b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests-data.js
@@ -0,0 +1,96 @@
+export const kTestFolderName = "image-tests";
+
+// The internal priorities are specified as implementation-defined,
+// (https://fetch.spec.whatwg.org/#concept-fetch, step 15). For web-
+// compatibility, Chromium's mapping is chosen
+// (https://web.dev/articles/fetch-priority#browser_priority_and_fetchpriority).
+const kExpectedRequestsOfInitialLoad = [
+ { fileNameAndSuffix: "square_25px_x_25px.png?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+];
+
+const kExpectedRequestsOfInitialLoadDisabled = [
+ { fileNameAndSuffix: "square_25px_x_25px.png?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+];
+
+const kExpectedRequestsOfDynamicLoad = kExpectedRequestsOfInitialLoad;
+
+const kExpectedRequestsOfDynamicLoadDisabled = kExpectedRequestsOfInitialLoadDisabled;
+
+const kExpectedRequestsOfInitialLoadForSVGImageTagDisabled = kExpectedRequestsOfInitialLoadDisabled;
+
+// TODO(bug 1865837): Should SVG's `<image>` element support the `fetchpriority` attribute?
+const kExpectedRequestsOfInitialLoadForSVGImageTag = kExpectedRequestsOfInitialLoadForSVGImageTagDisabled;
+
+const kExpectedRequestsOfDynamicLoadForSVGImageTagDisabled = kExpectedRequestsOfDynamicLoadDisabled;
+
+// TODO(bug 1865837): Should SVG's `<image>` element support the `fetchpriority` attribute?
+const kExpectedRequestsOfDynamicLoadForSVGImageTag = kExpectedRequestsOfDynamicLoadForSVGImageTagDisabled;
+
+const kExpectedRequestsShapeOutsideImage = [
+ { fileNameAndSuffix: "square_25px_x_25px.png?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "square_25px_x_25px.png?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+];
+
+const kExpectedRequestsShapeOutsideImageDisabled = kExpectedRequestsShapeOutsideImage;
+
+export const kTestData = [
+ { testFileName: "image-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsOfInitialLoad
+ },
+ { testFileName: "image-dynamic-load.h2.html",
+ expectedRequests: kExpectedRequestsOfDynamicLoad
+ },
+ { testFileName: "image-svg-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsOfInitialLoadForSVGImageTag,
+ },
+ { testFileName: "image-svg-dynamic-load.h2.html",
+ expectedRequests: kExpectedRequestsOfDynamicLoadForSVGImageTag,
+ },
+ { testFileName: "shape-outside-image.h2.html",
+ expectedRequests: kExpectedRequestsShapeOutsideImage
+ },
+];
+
+export const kTestDataDisabled = [
+ { testFileName: "image-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsOfInitialLoadDisabled
+ },
+ { testFileName: "image-dynamic-load.h2.html",
+ expectedRequests: kExpectedRequestsOfDynamicLoadDisabled
+ },
+ { testFileName: "image-svg-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsOfInitialLoadForSVGImageTagDisabled,
+ },
+ { testFileName: "image-svg-dynamic-load.h2.html",
+ expectedRequests: kExpectedRequestsOfDynamicLoadForSVGImageTagDisabled,
+ },
+ { testFileName: "shape-outside-image.h2.html",
+ expectedRequests: kExpectedRequestsShapeOutsideImageDisabled
+ },
+];
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-dynamic-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-dynamic-load.h2.html
new file mode 100644
index 0000000000..20de6ed042
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-dynamic-load.h2.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { src: "../resources/square_25px_x_25px.png?1", fetchPriority: "low" },
+ { src: "../resources/square_25px_x_25px.png?2", fetchPriority: "high" },
+ { src: "../resources/square_25px_x_25px.png?3", fetchPriority: "auto" },
+ { src: "../resources/square_25px_x_25px.png?4"},
+ ];
+
+ let numberOfLoads = 0;
+ for (const data of kData) {
+ const imgElement = document.createElement("img");
+
+ if ("fetchPriority" in data) {
+ imgElement.fetchPriority = data.fetchPriority;
+ }
+
+ imgElement.addEventListener("load", () => {
+ ++numberOfLoads;
+ if (numberOfLoads == kData.length) {
+ opener.postMessage("ChildLoaded");
+ }
+ }, { once: "true"});
+
+ imgElement.alt = "a";
+ imgElement.src = data.src;
+
+ // Don't append `imgElement` to the document to prevent re-
+ // priotiziation.
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-initial-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-initial-load.h2.html
new file mode 100644
index 0000000000..c3ee532d9e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-initial-load.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ </head>
+ <body>
+ <img fetchpriority="low" src="../resources/square_25px_x_25px.png?2" alt="img">
+ <img fetchpriority="high" src="../resources/square_25px_x_25px.png?1" alt="img">
+ <img fetchpriority="auto" src="../resources/square_25px_x_25px.png?3" alt="img">
+ <img src="../resources/square_25px_x_25px.png?4" alt="img">
+ <script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-dynamic-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-dynamic-load.h2.html
new file mode 100644
index 0000000000..81df28cd8a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-dynamic-load.h2.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>internal priority of SVG's &lt;image&gt; element (dynamic insertion)</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { src: "../resources/square_25px_x_25px.png?1", fetchPriority: "low" },
+ { src: "../resources/square_25px_x_25px.png?2", fetchPriority: "high" },
+ { src: "../resources/square_25px_x_25px.png?3", fetchPriority: "auto" },
+ { src: "../resources/square_25px_x_25px.png?4"},
+ ];
+ let numberOfLoads = 0;
+ for (const data of kData) {
+ const namespaceURI = "http://www.w3.org/2000/svg";
+ const imgElement = document.createElementNS(namespaceURI, "image");
+
+ if ("fetchPriority" in data) {
+ imgElement.fetchPriority = data.fetchPriority;
+ }
+
+ imgElement.addEventListener("load", () => {
+ ++numberOfLoads;
+ if (numberOfLoads == kData.length) {
+ opener.postMessage("ChildLoaded");
+ }
+ }, { once: "true"});
+
+ // Use setAttribute as corresponding SVGImageElement/SVGURIReference
+ // attributes are read-only.
+ imgElement.setAttribute("width", "25");
+ imgElement.setAttribute("height", "25");
+ imgElement.setAttribute("href", data.src);
+
+ // Per https://svgwg.org/svg2-draft/linking.html#processingURL
+ // the URL is only processed "at the time the element is connected to a
+ // document, or at the time when the attribute is set, whichever is
+ // later" so it's necessary to insert imgElement into the document in
+ // order to trigger ressource fetching.
+ let svg = document.createElementNS(namespaceURI, "svg");
+ svg.appendChild(imgElement);
+ document.body.appendChild(svg);
+ }
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-initial-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-initial-load.h2.html
new file mode 100644
index 0000000000..d8d46e37c8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/image-svg-initial-load.h2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>internal priority of SVG's &lt;image&gt; element</title>
+ </head>
+ <body>
+ <svg width="25" height="25">
+ <image href="../resources/square_25px_x_25px.png?1"
+ width="25" height="25" fetchpriority="low"/>
+ </svg>
+ <svg width="25" height="25">
+ <image href="../resources/square_25px_x_25px.png?2"
+ width="25" height="25" fetchpriority="high"/>
+ </svg>
+ <svg width="25" height="25">
+ <image href="../resources/square_25px_x_25px.png?3"
+ width="25" height="25" fetchpriority="auto"/>
+ </svg>
+ <svg width="25" height="25">
+ <image href="../resources/square_25px_x_25px.png?4"
+ width="25" height="25"/>
+ </svg>
+ <script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/shape-outside-image.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/shape-outside-image.h2.html
new file mode 100644
index 0000000000..7668a3292c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/image-tests/shape-outside-image.h2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>internal priority of shape-outside image</title>
+ <style>
+ .floated-box {
+ border: solid;
+ width: 150px;
+ height: 150px;
+ float: right;
+ shape-outside: url("../resources/square_25px_x_25px.png?1");
+ }
+ </style>
+</head>
+<body>
+<!-- The url for the src and `shape-outside` may differ per spec
+ (https://drafts.csswg.org/css-shapes/#shape-outside-property) . !-->
+<img class=floated-box src="../resources/square_25px_x_25px.png?2" alt="img with shape-outside image"></img>
+
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
+
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded")
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests-data.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests-data.js
new file mode 100644
index 0000000000..16e59f2fd9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests-data.js
@@ -0,0 +1,471 @@
+export const kTestFolderName = "link-tests";
+
+const kExpectedRequestsOfLoadStylesheet = [
+ { fileNameAndSuffix: "dummy.css?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?5",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?6",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?7",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?8",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ // `media=print` doesn't match the environment
+ // (https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#matches-the-environment)
+ // hence all internal priorities should be low.
+ { fileNameAndSuffix: "dummy.css?9",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?10",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?11",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.css?12",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+];
+
+const kExpectedRequestsOfLoadStylesheetDisabled = [
+ { fileNameAndSuffix: "dummy.css?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?5",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?6",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?7",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?8",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?9",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?10",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?11",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.css?12",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadFont = [
+ { fileNameAndSuffix: "dummy.font?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.font?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.font?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.font?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadFontDisabled = [
+ { fileNameAndSuffix: "dummy.font?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.font?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.font?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.font?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadImage = [
+ { fileNameAndSuffix: "dummy.image?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.image?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.image?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.image?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadImageDisabled = [
+ { fileNameAndSuffix: "dummy.image?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW + 1
+ },
+ { fileNameAndSuffix: "dummy.image?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW + 1
+ },
+ { fileNameAndSuffix: "dummy.image?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW + 1
+ },
+ { fileNameAndSuffix: "dummy.image?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW + 1
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadFetch = [
+ { fileNameAndSuffix: "dummy.txt?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.txt?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.txt?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.txt?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadFetchDisabled = [
+ { fileNameAndSuffix: "dummy.txt?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.txt?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.txt?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.txt?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+];
+
+const kExpectedRequestsOfPreloadScript = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+];
+
+const kExpectedRequestsOfPreloadScriptDisabled = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadStyle = [
+ { fileNameAndSuffix: "dummy.css?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.css?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+];
+
+const kExpectedRequestsOfLinkPreloadStyleDisabled = [
+ { fileNameAndSuffix: "dummy.css?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+ { fileNameAndSuffix: "dummy.css?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGHEST
+ },
+];
+
+const kExpectedRequestsOfModulepreload = kExpectedRequestsOfPreloadScript;
+
+const kExpectedRequestsOfModulepreloadDisabled = kExpectedRequestsOfPreloadScriptDisabled;
+
+const kExpectedRequestsOfPrefetch = [
+ { fileNameAndSuffix: "dummy.txt?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOWEST
+ },
+ { fileNameAndSuffix: "dummy.txt?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOWEST
+ },
+ { fileNameAndSuffix: "dummy.txt?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOWEST
+ },
+ { fileNameAndSuffix: "dummy.txt?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOWEST
+ }
+];
+
+const kExpectedRequestsOfPrefetchDisabled = kExpectedRequestsOfPrefetch;
+
+const kPipeHeaderLinksToStylesheets =
+ "=header(Link,<dummy.css?1>; rel=stylesheet; fetchpriority=low,True)" +
+ "|header(Link,<dummy.css?2>; rel=stylesheet; fetchpriority=high,True)" +
+ "|header(Link,<dummy.css?3>; rel=stylesheet; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.css?4>; rel=stylesheet,True)" +
+ "|header(Link,<dummy.css?5>; rel=\"alternate stylesheet\"; title=5; fetchpriority=low,True)" +
+ "|header(Link,<dummy.css?6>; rel=\"alternate stylesheet\"; title=6; fetchpriority=high,True)" +
+ "|header(Link,<dummy.css?7>; rel=\"alternate stylesheet\"; title=7; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.css?8>; rel=\"alternate stylesheet\"; title=8,True)" +
+ "|header(Link,<dummy.css?9>; rel=stylesheet; fetchpriority=low; media=print,True)" +
+ "|header(Link,<dummy.css?10>; rel=stylesheet; fetchpriority=high; media=print,True)" +
+ "|header(Link,<dummy.css?11>; rel=stylesheet; fetchpriority=auto; media=print,True)" +
+ "|header(Link,<dummy.css?12>; rel=stylesheet; media=print,True)";
+
+const kPipeHeaderPreloadFontLinks =
+ "=header(Link,<dummy.font?1>; rel=preload; as=font; fetchpriority=low,True)" +
+ "|header(Link,<dummy.font?2>; rel=preload; as=font; fetchpriority=high,True)" +
+ "|header(Link,<dummy.font?3>; rel=preload; as=font; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.font?4>; rel=preload; as=font,True)";
+
+const kPipeHeaderPreloadImageLinks =
+ "=|header(Link,<dummy.image?1>; rel=preload; as=image; fetchpriority=low,True)" +
+ "|header(Link,<dummy.image?2>; rel=preload; as=image; fetchpriority=high,True)" +
+ "|header(Link,<dummy.image?3>; rel=preload; as=image; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.image?4>; rel=preload; as=image,True)";
+
+const kPipeHeaderPreloadFetchLinks =
+ "=header(Link,<dummy.txt?1>; rel=preload; as=fetch; fetchpriority=low,True)" +
+ "|header(Link,<dummy.txt?2>; rel=preload; as=fetch; fetchpriority=high,True)" +
+ "|header(Link,<dummy.txt?3>; rel=preload; as=fetch; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.txt?4>; rel=preload; as=fetch,True)";
+
+const kPipeHeaderPreloadScriptLinks =
+ "=header(Link,<dummy.js?1>; rel=preload; as=script; fetchpriority=low,True)" +
+ "|header(Link,<dummy.js?2>; rel=preload; as=script; fetchpriority=high,True)" +
+ "|header(Link,<dummy.js?3>; rel=preload; as=script; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.js?4>; rel=preload; as=script,True)";
+
+ const kPipeHeaderPreloadStyleLinks =
+ "=header(Link,<dummy.css?1>; rel=preload; as=style; fetchpriority=low,True)" +
+ "|header(Link,<dummy.css?2>; rel=preload; as=style; fetchpriority=high,True)" +
+ "|header(Link,<dummy.css?3>; rel=preload; as=style; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.css?4>; rel=preload; as=style,True)";
+
+const kPipeHeaderModulepreloadLinks =
+ "=header(Link,<dummy.js?1>; rel=modulepreload; fetchpriority=low,True)" +
+ "|header(Link,<dummy.js?2>; rel=modulepreload; fetchpriority=high,True)" +
+ "|header(Link,<dummy.js?3>; rel=modulepreload; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.js?4>; rel=modulepreload,True)";
+
+const kPipeHeaderPrefetchLinks =
+ "=header(Link,<dummy.txt?1>; rel=prefetch; fetchpriority=low,True)" +
+ "|header(Link,<dummy.txt?2>; rel=prefetch; fetchpriority=high,True)" +
+ "|header(Link,<dummy.txt?3>; rel=prefetch; fetchpriority=auto,True)" +
+ "|header(Link,<dummy.txt?4>; rel=prefetch,True)";
+
+// The expected internal priorites of the test data are specified as
+// implementation-defined. See step 11. of
+// <https://html.spec.whatwg.org/#create-a-link-request> and step 15. of
+// <https://fetch.spec.whatwg.org/#concept-fetch>.
+//
+// The internal priorities already differ for browsers. The ones for Chromium,
+// including fetchpriority's effect on them, are reported at
+// <https://web.dev/fetch-priority/#browser-priority-and-fetchpriority>.
+// When Gecko's internal priorities match those, the fetchpriority attribute in
+// Gecko is expected to have the same effect as in Chromium. When not, applying
+// "fetchpriority=low" ("high") is expected to adjust the internal priority to
+// the next lower (higher) priority.
+export const kTestData = [
+ { testFileName: "link-initial-load-stylesheet.h2.html",
+ expectedRequests: kExpectedRequestsOfLoadStylesheet
+ },
+ { testFileName: "link-dynamic-load-stylesheet.h2.html",
+ expectedRequests: kExpectedRequestsOfLoadStylesheet
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderLinksToStylesheets,
+ expectedRequests: kExpectedRequestsOfLoadStylesheet
+ },
+ { testFileName: "link-initial-preload-image.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadImage
+ },
+ { testFileName: "link-initial-preload-font.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFont
+ },
+ { testFileName: "link-initial-preload-fetch.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFetch
+ },
+ { testFileName: "link-initial-preload-script.h2.html",
+ expectedRequests: kExpectedRequestsOfPreloadScript
+ },
+ { testFileName: "link-initial-preload-style.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadStyle
+ },
+ { testFileName: "link-dynamic-preload-image.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadImage
+ },
+ { testFileName: "link-dynamic-preload-font.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFont
+ },
+ { testFileName: "link-dynamic-preload-fetch.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFetch
+ },
+ { testFileName: "link-dynamic-preload-script.h2.html",
+ expectedRequests: kExpectedRequestsOfPreloadScript
+ },
+ { testFileName: "link-dynamic-preload-style.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadStyle
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadImageLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadImage
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadFontLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadFont
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadFetchLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadFetch
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadScriptLinks,
+ expectedRequests: kExpectedRequestsOfPreloadScript
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadStyleLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadStyle
+ },
+ { testFileName: "link-initial-modulepreload.h2.html",
+ expectedRequests: kExpectedRequestsOfModulepreload
+ },
+ { testFileName: "link-dynamic-modulepreload.h2.html",
+ expectedRequests: kExpectedRequestsOfModulepreload
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderModulepreloadLinks,
+ expectedRequests: kExpectedRequestsOfModulepreload
+ },
+ { testFileName: "link-initial-prefetch.h2.html",
+ expectedRequests: kExpectedRequestsOfPrefetch
+ },
+ { testFileName: "link-dynamic-prefetch.h2.html",
+ expectedRequests: kExpectedRequestsOfPrefetch
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPrefetchLinks,
+ expectedRequests: kExpectedRequestsOfPrefetch
+ }
+];
+
+export const kTestDataDisabled = [
+ { testFileName: "link-initial-load-stylesheet.h2.html",
+ expectedRequests: kExpectedRequestsOfLoadStylesheetDisabled
+ },
+ { testFileName: "link-initial-preload-font.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFontDisabled
+ },
+ { testFileName: "link-initial-preload-fetch.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFetchDisabled
+ },
+ { testFileName: "link-initial-preload-script.h2.html",
+ expectedRequests: kExpectedRequestsOfPreloadScriptDisabled
+ },
+ { testFileName: "link-initial-preload-style.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadStyleDisabled
+ },
+ { testFileName: "link-initial-modulepreload.h2.html",
+ expectedRequests: kExpectedRequestsOfModulepreloadDisabled
+ },
+ { testFileName: "link-initial-prefetch.h2.html",
+ expectedRequests: kExpectedRequestsOfPrefetchDisabled
+ },
+ { testFileName: "link-initial-preload-image.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadImageDisabled
+ },
+ { testFileName: "link-dynamic-load-stylesheet.h2.html",
+ expectedRequests: kExpectedRequestsOfLoadStylesheetDisabled
+ },
+ { testFileName: "link-dynamic-prefetch.h2.html",
+ expectedRequests: kExpectedRequestsOfPrefetchDisabled
+ },
+ { testFileName: "link-dynamic-preload-font.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFontDisabled
+ },
+ { testFileName: "link-dynamic-preload-fetch.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadFetchDisabled
+ },
+ { testFileName: "link-dynamic-preload-script.h2.html",
+ expectedRequests: kExpectedRequestsOfPreloadScriptDisabled
+ },
+ { testFileName: "link-dynamic-preload-style.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadStyleDisabled
+ },
+ { testFileName: "link-dynamic-modulepreload.h2.html",
+ expectedRequests: kExpectedRequestsOfModulepreloadDisabled
+ },
+ { testFileName: "link-dynamic-preload-image.h2.html",
+ expectedRequests: kExpectedRequestsOfLinkPreloadImageDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderLinksToStylesheets,
+ expectedRequests: kExpectedRequestsOfLoadStylesheetDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPrefetchLinks,
+ expectedRequests: kExpectedRequestsOfPrefetchDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadStyleLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadStyleDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadFontLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadFontDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadFetchLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadFetchDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadImageLinks,
+ expectedRequests: kExpectedRequestsOfLinkPreloadImageDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderPreloadScriptLinks,
+ expectedRequests: kExpectedRequestsOfPreloadScriptDisabled
+ },
+ { testFileName: "link-header.h2.html?pipe" + kPipeHeaderModulepreloadLinks,
+ expectedRequests: kExpectedRequestsOfModulepreloadDisabled
+ },
+];
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-load-stylesheet.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-load-stylesheet.h2.html
new file mode 100644
index 0000000000..13a6b6f0f3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-load-stylesheet.h2.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { href: "../resources/dummy.css?1", fetchPriority: "low", isAlternate: false,
+ isMediaPrint: false },
+ { href: "../resources/dummy.css?2", fetchPriority: "high", isAlternate: false,
+ isMediaPrint: false },
+ { href: "../resources/dummy.css?3", fetchPriority: "auto", isAlternate: false,
+ isMediaPrint: false } ,
+ { href: "../resources/dummy.css?4", isAlternate: false,
+ isMediaPrint: false } ,
+ { href: "../resources/dummy.css?5", fetchPriority: "low", isAlternate: true,
+ isMediaPrint: false, title: "5" },
+ { href: "../resources/dummy.css?6", fetchPriority: "high", isAlternate: true,
+ isMediaPrint: false, title: "6" },
+ { href: "../resources/dummy.css?7", fetchPriority: "auto", isAlternate: true,
+ isMediaPrint: false, title: "7" },
+ { href: "../resources/dummy.css?8", isAlternate: true,
+ isMediaPrint: false, title: "8" },
+ { href: "../resources/dummy.css?9", fetchPriority: "low", isAlternate: false,
+ isMediaPrint: true },
+ { href: "../resources/dummy.css?10", fetchPriority: "high", isAlternate: false,
+ isMediaPrint: true },
+ { href: "../resources/dummy.css?11", fetchPriority: "auto", isAlternate: false,
+ isMediaPrint: true },
+ { href: "../resources/dummy.css?12", isAlternate: false,
+ isMediaPrint: true },
+ ];
+
+ let loadCounter = 0;
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+
+ linkElement.rel = data.isAlternate ? "alternate stylesheet" :
+ "stylesheet";
+
+ if (data.isAlternate) {
+ linkElement.title = data.title;
+ }
+
+ linkElement.href = data.href;
+
+ // Wait until all elements have been loaded.
+ linkElement.onload = () => {
+ ++loadCounter;
+ if (loadCounter == allLinkElements.length) {
+ opener.postMessage("ChildLoaded", "*");
+ }
+ }
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ if (data.isMediaPrint) {
+ linkElement.media = "print";
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-modulepreload.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-modulepreload.h2.html
new file mode 100644
index 0000000000..43a1e50813
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-modulepreload.h2.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { href: "../resources/dummy.js?1", fetchPriority: "low" },
+ { href: "../resources/dummy.js?2", fetchPriority: "high" },
+ { href: "../resources/dummy.js?3", fetchPriority: "auto" },
+ { href: "../resources/dummy.js?4" }
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "modulepreload";
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-prefetch.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-prefetch.h2.html
new file mode 100644
index 0000000000..2640596ee8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-prefetch.h2.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { href: "../resources/dummy.txt?1", fetchPriority: "low" },
+ { href: "../resources/dummy.txt?2", fetchPriority: "high" },
+ { href: "../resources/dummy.txt?3", fetchPriority: "auto" },
+ { href: "../resources/dummy.txt?4" }
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "prefetch";
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-fetch.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-fetch.h2.html
new file mode 100644
index 0000000000..b055a0e22c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-fetch.h2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { as: "fetch", href: "../resources/dummy.txt?1", fetchPriority: "low" },
+ { as: "fetch", href: "../resources/dummy.txt?2", fetchPriority: "high" },
+ { as: "fetch", href: "../resources/dummy.txt?3", fetchPriority: "auto" },
+ { as: "fetch", href: "../resources/dummy.txt?4" },
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "preload";
+ linkElement.as = data.as;
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-font.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-font.h2.html
new file mode 100644
index 0000000000..96e61c04b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-font.h2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { as: "font", href: "../resources/dummy.font?1", fetchPriority: "low" },
+ { as: "font", href: "../resources/dummy.font?2", fetchPriority: "high" },
+ { as: "font", href: "../resources/dummy.font?3", fetchPriority: "auto" },
+ { as: "font", href: "../resources/dummy.font?4" },
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "preload";
+ linkElement.as = data.as;
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-image.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-image.h2.html
new file mode 100644
index 0000000000..767a425ffc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-image.h2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { as: "image", href: "../resources/dummy.image?1", fetchPriority: "low" },
+ { as: "image", href: "../resources/dummy.image?2", fetchPriority: "high" },
+ { as: "image", href: "../resources/dummy.image?3", fetchPriority: "auto" },
+ { as: "image", href: "../resources/dummy.image?4" },
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "preload";
+ linkElement.as = data.as;
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-script.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-script.h2.html
new file mode 100644
index 0000000000..5c46c9004a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-script.h2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { as: "script", href: "../resources/dummy.js?1", fetchPriority: "low" },
+ { as: "script", href: "../resources/dummy.js?2", fetchPriority: "high" },
+ { as: "script", href: "../resources/dummy.js?3", fetchPriority: "auto" },
+ { as: "script", href: "../resources/dummy.js?4" },
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "preload";
+ linkElement.as = data.as;
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-style.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-style.h2.html
new file mode 100644
index 0000000000..e69364c50b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-dynamic-preload-style.h2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { as: "style", href: "../resources/dummy.css?1", fetchPriority: "low" },
+ { as: "style", href: "../resources/dummy.css?2", fetchPriority: "high" },
+ { as: "style", href: "../resources/dummy.css?3", fetchPriority: "auto" },
+ { as: "style", href: "../resources/dummy.css?4" }
+ ];
+
+ let allLinkElements = [];
+ for (data of kData) {
+ let linkElement = document.createElement("link");
+ linkElement.rel = "preload";
+ linkElement.as = data.as;
+ linkElement.href = data.href;
+
+ if ("fetchPriority" in data) {
+ linkElement.fetchPriority = data.fetchPriority;
+ }
+
+ allLinkElements.push(linkElement);
+ }
+
+ document.head.append(...allLinkElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-header.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-header.h2.html
new file mode 100644
index 0000000000..6b50fe3543
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-header.h2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-load-stylesheet.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-load-stylesheet.h2.html
new file mode 100644
index 0000000000..e235ad0d49
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-load-stylesheet.h2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="stylesheet" href="../resources/dummy.css?1" fetchpriority="low">
+ <link rel="stylesheet" href="../resources/dummy.css?2" fetchpriority="high">
+ <link rel="stylesheet" href="../resources/dummy.css?3" fetchpriority="auto">
+ <link rel="stylesheet" href="../resources/dummy.css?4">
+ <link rel="alternate stylesheet" title="5" href="../resources/dummy.css?5" fetchpriority="low">
+ <link rel="alternate stylesheet" title="6" href="../resources/dummy.css?6" fetchpriority="high">
+ <link rel="alternate stylesheet" title="7" href="../resources/dummy.css?7" fetchpriority="auto">
+ <link rel="alternate stylesheet" title="8" href="../resources/dummy.css?8">
+ <link rel="stylesheet" href="../resources/dummy.css?9" fetchpriority="low" media="print">
+ <link rel="stylesheet" href="../resources/dummy.css?10" fetchpriority="high" media="print">
+ <link rel="stylesheet" href="../resources/dummy.css?11" fetchpriority="auto" media="print">
+ <link rel="stylesheet" href="../resources/dummy.css?12" media="print">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-modulepreload.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-modulepreload.h2.html
new file mode 100644
index 0000000000..365695ff1e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-modulepreload.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="modulepreload" href="../resources/dummy.js?1" fetchpriority="low">
+ <link rel="modulepreload" href="../resources/dummy.js?2" fetchpriority="high">
+ <link rel="modulepreload" href="../resources/dummy.js?3" fetchpriority="auto">
+ <link rel="modulepreload" href="../resources/dummy.js?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-prefetch.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-prefetch.h2.html
new file mode 100644
index 0000000000..746997aca2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-prefetch.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="prefetch" href="../resources/dummy.txt?1" fetchpriority="low">
+ <link rel="prefetch" href="../resources/dummy.txt?2" fetchpriority="high">
+ <link rel="prefetch" href="../resources/dummy.txt?3" fetchpriority="auto">
+ <link rel="prefetch" href="../resources/dummy.txt?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-fetch.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-fetch.h2.html
new file mode 100644
index 0000000000..891425d1ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-fetch.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="preload" as="fetch" href="../resources/dummy.txt?1" fetchpriority="low">
+ <link rel="preload" as="fetch" href="../resources/dummy.txt?2" fetchpriority="high">
+ <link rel="preload" as="fetch" href="../resources/dummy.txt?3" fetchpriority="auto">
+ <link rel="preload" as="fetch" href="../resources/dummy.txt?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-font.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-font.h2.html
new file mode 100644
index 0000000000..32228a99dc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-font.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="preload" as="font" href="../resources/dummy.font?1" fetchpriority="low">
+ <link rel="preload" as="font" href="../resources/dummy.font?2" fetchpriority="high">
+ <link rel="preload" as="font" href="../resources/dummy.font?3" fetchpriority="auto">
+ <link rel="preload" as="font" href="../resources/dummy.font?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-image.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-image.h2.html
new file mode 100644
index 0000000000..0dc1b482b4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-image.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="preload" as="image" href="../resources/dummy.image?1" fetchpriority="low">
+ <link rel="preload" as="image" href="../resources/dummy.image?2" fetchpriority="high">
+ <link rel="preload" as="image" href="../resources/dummy.image?3" fetchpriority="auto">
+ <link rel="preload" as="image" href="../resources/dummy.image?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-script.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-script.h2.html
new file mode 100644
index 0000000000..bcc23751bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-script.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="preload" as="script" href="../resources/dummy.js?1" fetchpriority="low">
+ <link rel="preload" as="script" href="../resources/dummy.js?2" fetchpriority="high">
+ <link rel="preload" as="script" href="../resources/dummy.js?3" fetchpriority="auto">
+ <link rel="preload" as="script" href="../resources/dummy.js?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-style.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-style.h2.html
new file mode 100644
index 0000000000..77aba48907
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/link-tests/link-initial-preload-style.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <link rel="preload" as="style" href="../resources/dummy.css?1" fetchpriority="low">
+ <link rel="preload" as="style" href="../resources/dummy.css?2" fetchpriority="high">
+ <link rel="preload" as="style" href="../resources/dummy.css?3" fetchpriority="auto">
+ <link rel="preload" as="style" href="../resources/dummy.css?4">
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.css b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.css
new file mode 100644
index 0000000000..d9a4c4e1b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.css
@@ -0,0 +1 @@
+p { color: green; }
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.font b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.font
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.font
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.image b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.image
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.image
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.js
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.txt b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.txt
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/square_25px_x_25px.png b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/square_25px_x_25px.png
new file mode 100644
index 0000000000..33015ff4a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/square_25px_x_25px.png
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests-data.js b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests-data.js
new file mode 100644
index 0000000000..8f3b2033ad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests-data.js
@@ -0,0 +1,216 @@
+const kFetchPriorityLowRequestFileNameAndSuffix = "dummy.js?1";
+const kFetchPriorityHighRequestFileNameAndSuffix = "dummy.js?2";
+const kFetchPriorityAutoRequestFileNameAndSuffix = "dummy.js?3";
+const kNoFetchPriorityRequestFileNameAndSuffix = "dummy.js?4";
+
+// Mapping fetchpriority's values to internal priorities is specified as
+// implementation-defined (https://fetch.spec.whatwg.org/#concept-fetch, step
+// 15). For web-compatibility, Chromium's desired mapping is chosen, see
+// <https://web.dev/articles/fetch-priority#browser_priority_and_fetchpriority>.
+// Exceptions are commented below.
+
+const kExpectedRequestsForScriptsInHead = [
+ { fileNameAndSuffix: kFetchPriorityLowRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: kFetchPriorityHighRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: kFetchPriorityAutoRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: kNoFetchPriorityRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ }
+];
+
+const kExpectedRequestsForScriptsInHeadDisabled = [
+ { fileNameAndSuffix: kFetchPriorityLowRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: kFetchPriorityHighRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: kFetchPriorityAutoRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: kNoFetchPriorityRequestFileNameAndSuffix,
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ }
+];
+
+const kExpectedRequestsForScriptsInBody = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ // Bug 1872654: Chromium's behavior here differs.
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?5",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.js?6",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.js?7",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?8",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+]
+
+const kExpectedRequestsForScriptsInBodyDisabled = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?5",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?6",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?7",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?8",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+]
+
+export const kTestFolderName = "script-tests";
+
+const kExpectedRequestsForNonModuleAsyncAndDeferredScripts = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+]
+
+const kExpectedRequestsForNonModuleAsyncAndDeferredScriptsDisabled = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+]
+
+// Chromium's desired behavior is under discussion:
+// <https://bugs.chromium.org/p/chromium/issues/detail?id=1475635>.
+const kExpectedRequestsForModuleScripts = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_LOW
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_HIGH
+ },
+]
+
+const kExpectedRequestsForModuleScriptsDisabled = [
+ { fileNameAndSuffix: "dummy.js?1",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?2",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?3",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+ { fileNameAndSuffix: "dummy.js?4",
+ internalPriority: SpecialPowers.Ci.nsISupportsPriority.PRIORITY_NORMAL
+ },
+]
+
+export const kTestData = [
+ { testFileName: "script-initial-load-head.h2.html",
+ expectedRequests: kExpectedRequestsForScriptsInHead
+ },
+ { testFileName: "script-initial-load-body.h2.html",
+ expectedRequests: kExpectedRequestsForScriptsInBody
+ },
+ { testFileName: "async-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForNonModuleAsyncAndDeferredScripts
+ },
+ { testFileName: "deferred-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForNonModuleAsyncAndDeferredScripts
+ },
+ { testFileName: "module-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForModuleScripts
+ },
+ { testFileName: "async-module-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForModuleScripts
+ },
+ // Dynamic insertion executes non-speculative-parsing
+ // (https://developer.mozilla.org/en-US/docs/Glossary/speculative_parsing)
+ // code paths. Moreover such inserted scripts are loaded asynchronously.
+ { testFileName: "script-dynamic-insertion.h2.html",
+ expectedRequests: kExpectedRequestsForNonModuleAsyncAndDeferredScripts
+ },
+ { testFileName: "module-script-dynamic-insertion.h2.html",
+ expectedRequests: kExpectedRequestsForModuleScripts
+ }
+];
+
+export const kTestDataDisabled = [
+ { testFileName: "script-initial-load-head.h2.html",
+ expectedRequests: kExpectedRequestsForScriptsInHeadDisabled
+ },
+ { testFileName: "script-initial-load-body.h2.html",
+ expectedRequests: kExpectedRequestsForScriptsInBodyDisabled
+ },
+ { testFileName: "async-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForNonModuleAsyncAndDeferredScriptsDisabled
+ },
+ { testFileName: "deferred-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForNonModuleAsyncAndDeferredScriptsDisabled
+ },
+ { testFileName: "module-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForModuleScriptsDisabled
+ },
+ { testFileName: "async-module-script-initial-load.h2.html",
+ expectedRequests: kExpectedRequestsForModuleScriptsDisabled
+ },
+ { testFileName: "script-dynamic-insertion.h2.html",
+ expectedRequests: kExpectedRequestsForNonModuleAsyncAndDeferredScriptsDisabled
+ },
+ { testFileName: "module-script-dynamic-insertion.h2.html",
+ expectedRequests: kExpectedRequestsForModuleScriptsDisabled
+ }
+];
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-module-script-initial-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-module-script-initial-load.h2.html
new file mode 100644
index 0000000000..6b8b0977b7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-module-script-initial-load.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <script type="module" async src="../resources/dummy.js?1" fetchpriority="low"></script>
+ <script type="module" async src="../resources/dummy.js?2" fetchpriority="high"></script>
+ <script type="module" async src="../resources/dummy.js?3" fetchpriority="auto"></script>
+ <script type="module" async src="../resources/dummy.js?4"></script>
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-script-initial-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-script-initial-load.h2.html
new file mode 100644
index 0000000000..f6ac1c9a4f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/async-script-initial-load.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <script async src="../resources/dummy.js?1" fetchpriority="low"></script>
+ <script async src="../resources/dummy.js?2" fetchpriority="high"></script>
+ <script async src="../resources/dummy.js?3" fetchpriority="auto"></script>
+ <script async src="../resources/dummy.js?4"></script>
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/deferred-script-initial-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/deferred-script-initial-load.h2.html
new file mode 100644
index 0000000000..99a4b8b3b9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/deferred-script-initial-load.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <script defer src="../resources/dummy.js?1" fetchpriority="low"></script>
+ <script defer src="../resources/dummy.js?2" fetchpriority="high"></script>
+ <script defer src="../resources/dummy.js?3" fetchpriority="auto"></script>
+ <script defer src="../resources/dummy.js?4"></script>
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-dynamic-insertion.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-dynamic-insertion.h2.html
new file mode 100644
index 0000000000..13e5b8c91d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-dynamic-insertion.h2.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { src: "../resources/dummy.js?1", fetchPriority: "low"},
+ { src: "../resources/dummy.js?2", fetchPriority: "high"},
+ { src: "../resources/dummy.js?3", fetchPriority: "auto"},
+ { src: "../resources/dummy.js?4"}
+ ];
+
+ let allScriptElements = [];
+ for (data of kData) {
+ let scriptElement = document.createElement("script");
+ scriptElement.src = data.src;
+
+ scriptElement.type = "module";
+
+ if ("fetchPriority" in data) {
+ scriptElement.fetchPriority = data.fetchPriority;
+ }
+
+ allScriptElements.push(scriptElement);
+ }
+
+ document.head.append(...allScriptElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-initial-load.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-initial-load.h2.html
new file mode 100644
index 0000000000..52c5d0e9ca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/module-script-initial-load.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <script type="module" src="../resources/dummy.js?1" fetchpriority="low"></script>
+ <script type="module" src="../resources/dummy.js?2" fetchpriority="high"></script>
+ <script type="module" src="../resources/dummy.js?3" fetchpriority="auto"></script>
+ <script type="module" src="../resources/dummy.js?4"></script>
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-dynamic-insertion.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-dynamic-insertion.h2.html
new file mode 100644
index 0000000000..27103c81c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-dynamic-insertion.h2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+<script>
+ onload = function() {
+ const kData = [
+ { src: "../resources/dummy.js?1", fetchPriority: "low"},
+ { src: "../resources/dummy.js?2", fetchPriority: "high"},
+ { src: "../resources/dummy.js?3", fetchPriority: "auto"},
+ { src: "../resources/dummy.js?4"}
+ ];
+
+ let allScriptElements = [];
+ for (data of kData) {
+ let scriptElement = document.createElement("script");
+ scriptElement.src = data.src;
+
+ if ("fetchPriority" in data) {
+ scriptElement.fetchPriority = data.fetchPriority;
+ }
+
+ allScriptElements.push(scriptElement);
+ }
+
+ document.head.append(...allScriptElements)
+
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-body.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-body.h2.html
new file mode 100644
index 0000000000..dd1c7eb60e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-body.h2.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+</head>
+<body>
+ <script src="../resources/dummy.js?1" fetchpriority="low"></script>
+ <script src="../resources/dummy.js?2" fetchpriority="high"></script>
+ <script src="../resources/dummy.js?3" fetchpriority="auto"></script>
+ <script src="../resources/dummy.js?4"></script>
+ <img src="../resources/square_25px_x_25px.png">
+ <!-- The image makes the external in-body
+ (https://html.spec.whatwg.org/#parsing-main-inbody) scripts considered "late"
+ (https://web.dev/articles/fetch-priority#browser_priority_and_fetchpriority).
+ -->
+ <script src="../resources/dummy.js?5" fetchpriority="low"></script>
+ <script src="../resources/dummy.js?6" fetchpriority="high"></script>
+ <script src="../resources/dummy.js?7" fetchpriority="auto"></script>
+ <script src="../resources/dummy.js?8"></script>
+ <script>
+ onload = function() {
+ opener.postMessage("ChildLoaded");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-head.h2.html b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-head.h2.html
new file mode 100644
index 0000000000..519bddf22f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/fetchpriority/support/script-tests/script-initial-load-head.h2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>fetchpriority</title>
+ <script src="../resources/dummy.js?1" fetchpriority="low"></script>
+ <script src="../resources/dummy.js?2" fetchpriority="high"></script>
+ <script src="../resources/dummy.js?3" fetchpriority="auto"></script>
+ <script src="../resources/dummy.js?4"></script>
+</head>
+<body>
+<script>
+ onload = function() {
+ opener.postMessage("ChildLoaded", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/fetch/orb/tentative/content-range.sub.window.js b/testing/web-platform/mozilla/tests/fetch/orb/tentative/content-range.sub.window.js
new file mode 100644
index 0000000000..ffc0d0286f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/orb/tentative/content-range.sub.window.js
@@ -0,0 +1,19 @@
+// META: script=/fetch/orb/resources/utils.js
+// META: script=resources/utils.js
+
+const url =
+ "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources/image.png";
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(
+ url,
+ { headers: new Headers([["Range", "bytes 10-99"]]) },
+ header("Content-Range", "bytes 10-99/1010"),
+ "slice(10,100)",
+ "status(206)"
+ ),
+ "ORB should filter opaque range of image/png not starting at zero, that isn't subsequent"
+);
diff --git a/testing/web-platform/mozilla/tests/fetch/orb/tentative/img-mime-types-coverage.tentative.sub.html b/testing/web-platform/mozilla/tests/fetch/orb/tentative/img-mime-types-coverage.tentative.sub.html
new file mode 100644
index 0000000000..d5ab1a4cd7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/orb/tentative/img-mime-types-coverage.tentative.sub.html
@@ -0,0 +1,43 @@
+<!-- Test verifies that cross-origin, nosniff images are 1) blocked when their
+ MIME type is covered by ORB and 2) allowed otherwise.
+
+ This test is very similar to fetch/orb/img-mime-types-coverage.tentative.sub.html,
+ except that it focuses on MIME types relevant to ORB.
+-->
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+ var passes = [
+ // These are exceptions that allow more MIME types than the ORB spec does.
+ // This is due to web compat, but might be removed in the future.
+ // See Bug 1828375
+ "application/dash+xml",
+ "application/vnd.apple.mpegurl",
+ "audio/mpegurl",
+ "audio/mpeg",
+ "text/vtt",
+ ]
+
+ const get_url = (mime) => {
+ // www1 is cross-origin, so the HTTP response is ORB-eligible -->
+ url = "http://{{domains[www1]}}:{{ports[http][0]}}"
+ url = url + "/fetch/nosniff/resources/image.py"
+ if (mime != null) {
+ url += "?type=" + encodeURIComponent(mime)
+ }
+ return url
+ }
+
+ passes.forEach(function (mime) {
+ async_test(function (t) {
+ var img = document.createElement("img")
+ img.onerror = t.unreached_func("Unexpected error event")
+ img.onload = t.step_func_done(function () {
+ assert_equals(img.width, 96)
+ })
+ img.src = get_url(mime)
+ document.body.appendChild(img)
+ }, "ORB should allow the response if Content-Type is: '" + mime + "'. ")
+ })
+</script>
diff --git a/testing/web-platform/mozilla/tests/fetch/orb/tentative/known-mime-type.sub.window.js b/testing/web-platform/mozilla/tests/fetch/orb/tentative/known-mime-type.sub.window.js
new file mode 100644
index 0000000000..0e2dd937c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/orb/tentative/known-mime-type.sub.window.js
@@ -0,0 +1,48 @@
+// META: script=/fetch/orb/resources/utils.js
+// META: script=resources/utils.js
+
+const path = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources";
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(`${path}/font.ttf`, null, contentType("font/ttf")),
+ "ORB should filter opaque font/ttf"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(`${path}/text.txt`, null, contentType("text/plain")),
+ "ORB should filter opaque text/plain"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(`${path}/data.json`, null, contentType("application/json")),
+ "ORB should filter opaque application/json (non-empty)"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(`${path}/empty.json`, null, contentType("application/json")),
+ "ORB should filter opaque application/json (empty)"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(
+ `${path}/data_non_ascii.json`,
+ null,
+ contentType("application/json")
+ ),
+ "ORB should filter opaque application/json which contains non ascii characters"
+);
diff --git a/testing/web-platform/mozilla/tests/fetch/orb/tentative/nosniff.sub.window.js b/testing/web-platform/mozilla/tests/fetch/orb/tentative/nosniff.sub.window.js
new file mode 100644
index 0000000000..4588b657c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/orb/tentative/nosniff.sub.window.js
@@ -0,0 +1,43 @@
+// META: script=/fetch/orb/resources/utils.js
+// META: script=resources/utils.js
+
+const path = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources";
+
+// This is an exception that allow more MIME types than the ORB spec does.
+// This is due to web compatibility, but might be removed in the future.
+// See Bug 1828375
+promise_test(
+ t => testFetchNoCors(
+ `${path}/text.txt`,
+ null,
+ contentType("text/plain"),
+ contentTypeOptions("nosniff")
+ ),
+ "ORB shouldn't block opaque text/plain with nosniff"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(
+ `${path}/data.json`,
+ null,
+ contentType("application/json"),
+ contentTypeOptions("nosniff")
+ ),
+ "ORB should filter opaque-response-blocklisted MIME type with nosniff"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(
+ `${path}/data.json`,
+ null,
+ contentType(""),
+ contentTypeOptions("nosniff")
+ ),
+ "ORB should filter opaque response with empty Content-Type and nosniff"
+);
diff --git a/testing/web-platform/mozilla/tests/fetch/orb/tentative/resources/utils.js b/testing/web-platform/mozilla/tests/fetch/orb/tentative/resources/utils.js
new file mode 100644
index 0000000000..bc8210f46b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/orb/tentative/resources/utils.js
@@ -0,0 +1,28 @@
+function testFetchNoCors(file, options, ...pipe) {
+ return fetch(`${file}${pipe.length ? `?pipe=${pipe.join("|")}` : ""}`, {
+ ...(options || {}),
+ mode: "no-cors",
+ });
+}
+
+function promise_internal_response_is_filtered(fetchPromise, message) {
+ return promise_test(async () => {
+ const response = await fetchPromise;
+
+ // A parent filtered opaque response is defined here as a response that isn't just an
+ // opaque response, but also where the internal response has been made unavailable.
+ // `Response.cloneUnfiltered` is used to inspect the state of the internal response,
+ // which is exactly what we want to be missing in this case.
+ const unfiltered = SpecialPowers.wrap(response).cloneUnfiltered();
+ assert_equals(
+ await SpecialPowers.unwrap(unfiltered).text(),
+ "",
+ "The internal response should be empty"
+ );
+ assert_equals(
+ Array.from(await SpecialPowers.unwrap(unfiltered).headers).length,
+ 0,
+ "The internal response should have no headers"
+ );
+ }, message);
+}
diff --git a/testing/web-platform/mozilla/tests/fetch/orb/tentative/status.sub.window.js b/testing/web-platform/mozilla/tests/fetch/orb/tentative/status.sub.window.js
new file mode 100644
index 0000000000..c09e4a1b24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/fetch/orb/tentative/status.sub.window.js
@@ -0,0 +1,30 @@
+// META: script=/fetch/orb/resources/utils.js
+// META: script=resources/utils.js
+
+const path = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources";
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(
+ `${path}/data.json`,
+ null,
+ contentType("application/json"),
+ "status(206)"
+ ),
+ "ORB should filter opaque-response-blocklisted MIME type with status 206"
+);
+
+// Due to web compatibility we filter opaque Response object from the
+// fetch() function in the Fetch specification. See Bug 1823877. This
+// might be removed in the future.
+promise_internal_response_is_filtered(
+ testFetchNoCors(
+ `${path}/data.json`,
+ null,
+ contentType("application/json"),
+ "status(302)"
+ ),
+ "ORB should filter opaque range of image/png not starting at zero, that isn't subsequent"
+);
diff --git a/testing/web-platform/mozilla/tests/focus/Range_collapse.html b/testing/web-platform/mozilla/tests/focus/Range_collapse.html
new file mode 100644
index 0000000000..7c16f16bec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_collapse.html
@@ -0,0 +1,207 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.collapse()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(true) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(false) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(false) of selection between start of the first text node of 'staticBefore' and end of the first text node of 'anchor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(false) of selection between start of the first text node of 'editor' and end of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(true) of selection between start of the first text node of 'editor' and end of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.collapse(false) of selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.collapse(true) of selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Range.collapse(false) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Range.collapse(true) of selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' when active element is 'anchor'");
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.collapse(true) of selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(false) of selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.collapse(true) of selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(false) of selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(true) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(false) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(true);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.collapse(true) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().getRangeAt(0).collapse(false);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.collapse(false) of selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' when active element is 'outerEditor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_selectNode.html b/testing/web-platform/mozilla/tests/focus/Range_selectNode.html
new file mode 100644
index 0000000000..3975653d2c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_selectNode.html
@@ -0,0 +1,267 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.selectNode() and Range.selectNodeContents()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+function selectNode(aNodeToSelect)
+{
+ document.getSelection().getRangeAt(0).selectNode(aNodeToSelect);
+}
+
+function selectNodeContents(aNodeToSelectItsContents)
+{
+ document.getSelection().getRangeAt(0).selectNodeContents(aNodeToSelectItsContents);
+}
+
+[{ func: selectNode, doingDescription: "Range.selectNode()" },
+ { func: selectNodeContents, doingDescription: "Range.selectNodeContents()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the <body>");
+
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'editor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'outerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(staticBefore.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with the first text node of 'staticBefore' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with the first text node of 'editor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with the first text node of 'outerEditor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(staticInEditor.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with the first text node of 'staticInEditor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with the first text node of 'innerEditor' when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ aTest.func(anchor.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with the first text node of 'anchor' when active element is the 'anchor'");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_setEnd.html b/testing/web-platform/mozilla/tests/focus/Range_setEnd.html
new file mode 100644
index 0000000000..e1eed4ae71
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_setEnd.html
@@ -0,0 +1,364 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.setEnd(), Range.setEndAfter() and Range.setEndBefore()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+function setEnd(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setEnd(aNode, aOffset);
+}
+
+function setEndBefore(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setEndBefore(aNode);
+}
+
+function setEndAfter(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setEndAfter(aNode);
+}
+
+// Range.setEnd*() should work same as collapse if specified end position is before its start.
+[{ func: setEnd, doingDescription: "Range.setEnd()" },
+ { func: setEndBefore, doingDescription: "Range.setEndBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (before the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.textNode);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(anchor.textNode);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (before the selection) when active element is 'anchor'");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticBefore.textNode, staticBefore.textLength);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the collapsed selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, editor.textLength);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (before the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticInEditor.textNode, staticInEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the collapsed selection) when active element is the <body> and selection is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the collapsed selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().collapse(anchor.textNode, anchor.textLength);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'anchor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (before the collapsed selection) when active element is 'anchor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(anchor.textNode, anchor.textLength);
+ aTest.func(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'anchor' (before the collapsed selection) when active element is the <body>");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, staticBefore.textLength,
+ editor.textNode, editor.textLength);
+ aTest.func(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'staticBefore' (before the selection, between end of the first text node of 'staticBefore' and end of the first text node of 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, editor.textLength,
+ outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (before the selection, between end of the first text node of 'editor' and end of the first text node of 'outerEditor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, outerEditor.textLength,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (before the selection, between end of the first text node of 'outerEditor' and end of the first text node of 'innerEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, staticInEditor.textLength,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'staticInEditor' (before the selection, between end of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor') when active element is the <body>");
+});
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticAfter.textNode);
+ setEndAfter(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setEndAfter() with the first text node of 'innerEditor' (before the selection) when active element is the <body> and selection is in 'staticAfter'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ setEndAfter(staticInEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setEndAfter() with the first text node of 'staticInEditor' (before the selection) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.textNode);
+ setEndAfter(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setEndAfter() with the first text node of 'outerEditor' (before the selection) when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ setEndAfter(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setEndAfter() with the first text node of 'outerEditor' (before the selection) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ setEndAfter(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setEndAfter() with the first text node of 'editor' (before the selection) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ setEndAfter(staticBefore.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setEndAfter() with the first text node of 'staticBefore' (before the selection) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(anchor.textNode);
+ setEndAfter(staticBefore.textNode);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setEndAfter() with the first text node of 'staticBefore' (before the selection) when active element is 'anchor'");
+
+// Range.setEnd*() should blur focused editing host when it expands selection to outside of it.
+[{ func: setEnd, doingDescription: "Range.setEnd()" },
+ { func: setEndAfter, doingDescription: "Range.setEndAfter()" },
+ { func: setEndBefore, doingDescription: "Range.setEndBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, 0);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'innerEditor' (after end of the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (after end of the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (after end of the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'innerEditor' (after end of the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'staticAfter' (after end of the selection) when active element is 'outerEditor'");
+});
+
+// Range.setEnd*() should move focus to an editing host when the range is shrunken into it.
+[{ func: setEnd, doingDescription: "Range.setEnd()" },
+ { func: setEndAfter, doingDescription: "Range.setEndAfter()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (shrunken into 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (shrunken into 'outerEditor') when active element is 'outerEditor' and selection end is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (shrunken into 'outerEditor') when active element is 'outerEditor' and selection end is in 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (shrunken into 'innerEditor') when active element is 'innerEditor' and selection end is in 'staticAfter'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (shrunken into 'staticInEditor') when active element is the <body>");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Range_setStart.html b/testing/web-platform/mozilla/tests/focus/Range_setStart.html
new file mode 100644
index 0000000000..9297aa02ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Range_setStart.html
@@ -0,0 +1,353 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Range.setStart(), Range.setStartAfter() and Range.setStartBefore()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+function setStart(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setStart(aNode, aOffset);
+}
+
+function setStartBefore(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setStartBefore(aNode);
+}
+
+function setStartAfter(aNode, aOffset)
+{
+ document.getSelection().getRangeAt(0).setStartAfter(aNode);
+}
+
+// Range.setStart*() should work same as collapse if specified start position is after its end.
+[{ func: setStart, doingDescription: "Range.setStart()" },
+ { func: setStartAfter, doingDescription: "Range.setStartAfter()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticBefore.textNode);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.textNode);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the selection) when active element is the <body> and selection is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticAfter' (before the selection) when active element is 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the collapsed selection) when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, 0);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the collapsed selection) when active element is the <body> and selection is in 'staticInEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, 0);
+ aTest.func(staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticAfter' (before the collapsed selection) when active element is 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, 0);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the selection, between start of the first text node of 'staticBefore' and start of the first text node of 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, 0);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the selection, between start of the first text node of 'editor' and start of the first text node of 'outerEditor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the selection, between start of the first text node of 'outerEditor' and start of the first text node of 'staticInEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the selection, between start of the first text node of 'outerEditor' and start of the first text node of 'innerEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ aTest.func(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'innerEditor' (before the selection, between start of the first text node of 'staticInEditor' and start of the first text node of 'innerEditor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, 0);
+ aTest.func(staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with end of the first text node of 'staticAfter' (before the selection, between start of the first text node of 'innerEditor' and start of the first text node of 'staticAfter') when active element is the <body>");
+});
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticBefore.textNode);
+ setStartBefore(editor.textNode);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Range.setStartBefore() with the first text node of 'editor' (before the selection) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ setStartBefore(outerEditor.textNode);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Range.setStartBefore() with the first text node of 'outerEditor' (before the selection) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ setStartBefore(innerEditor.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.setStartBefore() with the first text node of 'innerEditor' (before the selection) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ setStartBefore(staticAfter.textNode);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Range.setStartBefore() with the first text node of 'innerEditor' (before the selection) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticAfter.textNode);
+ setStartBefore(anchor.textNode);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Range.setStartBefore() with the first text node of 'anchor' (before the selection) when active element is the <body>");
+
+// Range.setStart*() should blur focused editing host when it expands selection to outside of it.
+[{ func: setStart, doingDescription: "Range.setStart()" },
+ { func: setStartAfter, doingDescription: "Range.setStartAfter()" },
+ { func: setStartBefore, doingDescription: "Range.setStartBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().collapse(editor.textNode, editor.textLength);
+ aTest.func(staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'staticBefore' (before the collapsed selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(outerEditor.textNode, outerEditor.textLength);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the collapsed selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, innerEditor.textLength);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the collapsed selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(innerEditor.textNode, innerEditor.textLength);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the collapsed selection) when active element is 'innerEditor'");
+
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.textNode);
+ aTest.func(staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with end of the first text node of 'staticBefore' (before the selection) when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.textNode);
+ aTest.func(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'editor' (before the selection) when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with end of the first text node of 'outerEditor' (before the selection) when active element is 'innerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.textNode);
+ aTest.func(staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with end of the first text node of 'staticInEditor' (before the selection) when active element is 'innerEditor'");
+});
+
+// Range.setStart*() should move focus to an editing host when the range is shrunken into it.
+[{ func: setStart, doingDescription: "Range.setStart()" },
+ { func: setStartBefore, doingDescription: "Range.setStartBefore()" }].forEach((aTest, aIndex, aArray)=>{
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ aTest.func(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor' after " + aTest.doingDescription + " with start of the first text node of 'editor' (shrunken into 'editor') when active element is the <body>");
+ test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ aTest.func(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'outerEditor' (shrunken into 'outerEditor') when active element is 'editor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ aTest.func(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'outerEditor' after " + aTest.doingDescription + " with start of the first text node of 'staticInEditor' (shrunken into 'staticInEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ aTest.func(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be 'innerEditor' after " + aTest.doingDescription + " with start of the first text node of 'innerEditor' (shrunken into 'innerEditor') when active element is 'outerEditor'");
+ test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ aTest.func(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ }, "Active element should be the <body> after " + aTest.doingDescription + " with start of the first text node of 'anchor' (shrunken into 'anchor') when active element is the <body>");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange.html
new file mode 100644
index 0000000000..d94e688158
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange.html
@@ -0,0 +1,1242 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.addRange()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+/**
+ * NOTE: When you add/modify something in this file, you should add same test to Selection_setBaseAndExtent.html too.
+ */
+
+
+// Selection.addRange() with collapsed range.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is the <body> and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with collapsed range at start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with collapsed range at start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with collapsed range at start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.addRange() with non-collapsed range in a node.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(staticBefore.textNode, staticBefore.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range in start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range in start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range in start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticAfter.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(anchor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range in start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.addRange() with a range across editing host boundary.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticBefore.textNode, 0);
+ range.setEnd(editor.textNode, editor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.textNode, 0);
+ range.setEnd(outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(staticInEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(outerEditor.textNode, 0);
+ range.setEnd(innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.addRange() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ var range = document.createRange();
+ range.setStart(innerEditor.textNode, 0);
+ range.setEnd(anchor.textNode, anchor.textLength);
+ document.getSelection().addRange(range);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.addRange() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html
new file mode 100644
index 0000000000..71b7c26c46
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange_in_iframe.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move and auto scroll tests caused by a call of Selection.addRange() into a contenteditable element in iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<iframe srcdoc="<div style='height: 1000px;'></div><div id='editor' contenteditable>editor</div>" style="height: 500px;"></iframe>
+<script>
+"use strict";
+setup({explicit_done:true});
+
+window.onload = ()=>{
+ test(function() {
+ var subDocument = document.querySelector("iframe").contentDocument;
+ var editorInFrame = subDocument.getElementById("editor");
+ var range = subDocument.createRange();
+ range.setStart(editorInFrame, 0);
+ var selection = subDocument.getSelection();
+ selection.removeAllRanges();
+ document.documentElement.scrollTop = 0;
+ subDocument.documentElement.scrollTop = 0;
+ selection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(subDocument.activeElement, editorInFrame);
+ assert_equals(subDocument.documentElement.scrollTop, 0);
+ }, "Moving selection into inactive contenteditable element in non-focused document shouldn't cause scrolling");
+
+ test(function() {
+ var iframe = document.querySelector("iframe");
+ var subDocument = iframe.contentDocument;
+ var selection = subDocument.getSelection();
+
+ // Reset selection in <iframe>
+ var editorInFrame = subDocument.getElementById("editor");
+ editorInFrame.blur();
+ selection.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(subDocument.body, 0);
+ selection.addRange(range);
+ subDocument.documentElement.scrollTop = 0;
+
+ // Move focus to the <iframe>
+ iframe.contentWindow.focus();
+ document.documentElement.scrollTop = 0;
+ assert_equals(document.activeElement, iframe);
+ assert_equals(subDocument.activeElement, subDocument.body);
+ assert_equals(subDocument.documentElement.scrollTop, 0);
+
+ range = subDocument.createRange();
+ range.setStart(editorInFrame, 0);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ assert_equals(document.activeElement, iframe);
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(subDocument.activeElement, editorInFrame);
+ assert_equals(subDocument.documentElement.scrollTop, 0);
+ }, "Moving selection into inactive contenteditable element in focused document shouldn't cause scrolling");
+
+ done();
+};
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html
new file mode 100644
index 0000000000..e06e9cd17a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.addRange() into iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body onload="doTest()">
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p>Here is an iframe:</p>
+<iframe src="Selection_addRange_into_iframe_iframe.html"></iframe>
+<script>
+"use strict";
+
+setup({explicit_done:true});
+
+function doTest() {
+ var selection = document.getSelection();
+ var childDocument = document.getElementsByTagName("iframe")[0].contentDocument;
+ var childSelection = childDocument.getSelection();
+ test(function() {
+ selection.collapse(document.body.firstChild, 0);
+ childSelection.collapse(childDocument.body.firstChild, 0);
+
+ document.documentElement.scrollTop = 0;
+ childDocument.documentElement.scrollTop = 0;
+ childSelection.removeAllRanges();
+ var range = childDocument.createRange();
+ range.selectNodeContents(childDocument.getElementById("editor1"));
+ childSelection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(childDocument.activeElement, childDocument.getElementById("editor1"));
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(childDocument.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor1' in the <iframe> after Selection.addRange() but parent's active document should be the <body>");
+ test(function() {
+ selection.collapse(document.body.firstChild, 0);
+ childSelection.collapse(childDocument.getElementById("editor1").firstChild, 0);
+
+ document.documentElement.scrollTop = 0;
+ childDocument.documentElement.scrollTop = 0;
+ childSelection.removeAllRanges();
+ var range = childDocument.createRange();
+ range.selectNodeContents(childDocument.getElementById("editor2"));
+ childSelection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(childDocument.activeElement, childDocument.getElementById("editor2"));
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(childDocument.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor2' in the <iframe> after Selection.addRange() but parent's active document should be the <body>");
+ test(function() {
+ selection.collapse(document.body.firstChild, 0);
+ childSelection.collapse(childDocument.getElementById("editor2").firstChild, 0);
+
+ document.documentElement.scrollTop = 0;
+ childDocument.documentElement.scrollTop = 0;
+ childSelection.removeAllRanges();
+ var range = childDocument.createRange();
+ range.selectNodeContents(childDocument.getElementById("non-editor"));
+ childSelection.addRange(range);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(childDocument.activeElement, childDocument.getElementById("editor2"));
+ assert_equals(document.documentElement.scrollTop, 0);
+ assert_equals(childDocument.documentElement.scrollTop, 0);
+ }, "Active element should be 'editor2' in the <iframe> after Selection.addRange() to non-editable <div> and parent's active document should be the <body>");
+
+ done();
+}
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html
new file mode 100644
index 0000000000..946d4aa5e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_addRange_into_iframe_iframe.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p>Here are two editors:</p>
+<div id="editor1" contenteditable>The first editor.</div>
+<div id="editor2" contenteditable>The second editor.</div>
+<div id="non-editor">The non-editable div.</div>
+</body>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_collapse.html b/testing/web-platform/mozilla/tests/focus/Selection_collapse.html
new file mode 100644
index 0000000000..2e3050d822
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_collapse.html
@@ -0,0 +1,148 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.collapse()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapse() not moving selection from first text node in the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().collapse(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapse() moving selection from first text node in the <body> to start of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'outerEditor' to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapse() moving selection from first text node in 'outerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of the <body>");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().collapse(anchor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapse() moving selection from first text node in 'innerEditor' to start of the first text node of 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html b/testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html
new file mode 100644
index 0000000000..f768782650
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_collapseToEnd.html
@@ -0,0 +1,134 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.collapseToEnd()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToEnd() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToEnd() with selection between start of the first text node of 'staticBefore' and end of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapseToEnd() with selection between start of the first text node of 'editor' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToEnd() with selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.collapseToEnd() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' and 'anchor' has focus before the call");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapseToEnd() with selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToEnd() with selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToEnd();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToEnd() with selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html b/testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html
new file mode 100644
index 0000000000..238a436c1b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_collapseToStart.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.collapseToStart()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToStart() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.collapseToStart() with selection between start of the first text node of 'editor' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'innerEditor' and end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.collapseToStart() with selection between start of the first text node of 'anchor' and end of the first text node of 'anchor' and 'anchor' has focus before the call");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.collapseToStart() with selection between start of the first text node of 'staticBefore' and end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'editor' and end of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ document.getSelection().collapseToStart();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.collapseToStart() with selection between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_extend.html b/testing/web-platform/mozilla/tests/focus/Selection_extend.html
new file mode 100644
index 0000000000..62136cc4a3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_extend.html
@@ -0,0 +1,189 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.extend()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(editor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(staticAfter.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'staticAfter'");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().extend(anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'staticBefore' to start of the first text node of 'anchor'");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().extend(editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to end of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().extend(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().extend(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.extend() from selection at start of the first text node of 'editor' to start of the first text node of 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to end of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(innerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().extend(editor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'outerEditor' to start of the first text node of 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to end of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().extend(anchor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.extend() from selection at start of the first text node of 'innerEditor' to start of the first text node of 'anchor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().extend(anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.extend() from selection at start of the first text node of 'anchor' to end of the first text node of 'anchor' and 'anchor' has focus before the call");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ anchor.element.blur();
+ document.getSelection().extend(anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.extend() from selection at start of the first text node of 'anchor' to end of the first text node of 'anchor' and 'anchor' doesn't have focus before the call");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html b/testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html
new file mode 100644
index 0000000000..6e6854d44b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_removeAllRanges.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.removeAllRanges()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange(staticBefore);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeAllRanges() when active element is the <body> and selection is at the start of the first text node of 'staticBefore'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.removeAllRanges() when active element is 'editor' and selection is at the start of the first text node of 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.removeAllRanges() when active element is 'outerEditor' and selection is at the start of the first text node of 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeAllRanges() when active element is the <body> and selection is at the start of the first text node of 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.removeAllRanges() when active element is 'innerEditor' and selection is at the start of the first text node of 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticAfter);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeAllRanges() when active element is the <body> and selection is at the start of the first text node of 'staticAfter'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeAllRanges();
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.removeAllRanges() when active element is 'anchor' and selection is at the start of the first text node of 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_removeRange.html b/testing/web-platform/mozilla/tests/focus/Selection_removeRange.html
new file mode 100644
index 0000000000..14e78381d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_removeRange.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.removeRange()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange(staticBefore);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeRange() to remove selected range at the start of the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.removeRange() to remove selected range at the start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.removeRange() to remove selected range at the start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeRange() to remove selected range at the start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.removeRange() to remove selected range at the start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticAfter);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.removeRange() to remove selected range at the start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().removeRange(document.getSelection().getRangeAt(0));
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.removeRange() to remove selected range at the start of the first text node of 'anchor' when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html b/testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html
new file mode 100644
index 0000000000..2753e60851
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_selectAllChildren.html
@@ -0,0 +1,254 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.selectAllChildren()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.selectAllChildren() to select the children of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'ouerEditor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(staticBefore.element);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.selectAllChildren() to select the children of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(editor.element);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.selectAllChildren() to select the children of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(outerEditor.element);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.selectAllChildren() to select the children of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(staticInEditor.element);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.selectAllChildren() to select the children of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(innerEditor.element);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.selectAllChildren() to select the children of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().selectAllChildren(anchor.element);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.selectAllChildren() to select the children of 'anchor' when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html b/testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html
new file mode 100644
index 0000000000..7cfc110009
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/Selection_setBaseAndExtent.html
@@ -0,0 +1,926 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.setBaseAndExtent()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<div style="height: 3000px;">Spacer to check whether or not page was scrolled down to focused editor</div>
+<p id="staticBefore">static text</p>
+<div id="editor" contenteditable><p>content of editor</p></div>
+<div id="outerEditor" contenteditable
+><p>content of outer editor</p><div id="staticInEditor" contenteditable="false"
+><p>static content of outer editor</p><div id="innerEditor" contenteditable
+><p>content of inner editor</p></div></div></div>
+<p id="staticAfter">static text</p>
+<p><a id="anchor" href="about:blank">anchor</a></p>
+<script>
+"use strict";
+
+var staticBefore = {
+ element: document.getElementById("staticBefore"),
+ textNode: document.getElementById("staticBefore").firstChild,
+ textLength: document.getElementById("staticBefore").firstChild.length
+};
+var editor = {
+ element: document.getElementById("editor"),
+ textNode: document.getElementById("editor").firstChild.firstChild,
+ textLength: document.getElementById("editor").firstChild.firstChild.length
+};
+var outerEditor = {
+ element: document.getElementById("outerEditor"),
+ textNode: document.getElementById("outerEditor").firstChild.firstChild,
+ textLength: document.getElementById("outerEditor").firstChild.firstChild.length
+};
+var staticInEditor = {
+ element: document.getElementById("staticInEditor"),
+ textNode: document.getElementById("staticInEditor").firstChild,
+ textLength: document.getElementById("staticInEditor").firstChild.length
+};
+var innerEditor = {
+ element: document.getElementById("innerEditor"),
+ textNode: document.getElementById("innerEditor").firstChild.firstChild,
+ textLength: document.getElementById("innerEditor").firstChild.firstChild.length
+};
+var staticAfter = {
+ element: document.getElementById("staticAfter"),
+ textNode: document.getElementById("staticAfter").firstChild,
+ textLength: document.getElementById("staticAfter").firstChild.length
+};
+var anchor = {
+ element: document.getElementById("anchor"),
+ textNode: document.getElementById("anchor").firstChild,
+ textLength: document.getElementById("anchor").firstChild.length
+};
+
+function resetFocusAndSelectionRange(aFocus)
+{
+ document.getSelection().removeAllRanges();
+ if (document.activeElement) {
+ document.activeElement.blur();
+ }
+ if (aFocus) {
+ aFocus.element.focus();
+ document.getSelection().collapse(aFocus.textNode, 0);
+ } else {
+ document.getSelection().collapse(staticBefore.textNode, 0);
+ }
+ document.documentElement.scrollTop = 0;
+}
+
+/**
+ * NOTE: When you add/modify something in this file, you should add same test to Selection_addRange.html too.
+ */
+
+// Selection.setBaseAndExtent() with collapsed range.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is the <body> and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is the <body> and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, 0);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, 0);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, 0);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, 0);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with collapsed range at start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.setBaseAndExtent() with non-collapsed range in a node.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+test(function() {
+ resetFocusAndSelectionRange(staticInEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'outerEditor' and selection is in 'staticInEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ staticBefore.textNode, staticBefore.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in the first text node of 'staticBefore' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'editor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'outerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticInEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range in start of the first text node of 'innerEditor' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticAfter.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'staticAfter' when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(anchor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range in start of the first text node of 'anchor' when active element is 'anchor'");
+
+// Selection.setBaseAndExtent() with a range across editing host boundary.
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is the <body>");
+test(function() {
+ resetFocusAndSelectionRange();
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, document.body);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be the <body> after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is the <body>");
+
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'editor'");
+test(function() {
+ resetFocusAndSelectionRange(editor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, editor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'editor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'editor'");
+
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'outerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(outerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'outerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'innerEditor'");
+test(function() {
+ resetFocusAndSelectionRange(innerEditor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, innerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'innerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'innerEditor'");
+
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticBefore.textNode, 0,
+ editor.textNode, editor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticBefore' and end of the first text node of 'editor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(editor.textNode, 0,
+ outerEditor.textNode, outerEditor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'editor' and end of the first text node of 'outerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ staticInEditor.textNode, staticInEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'staticInEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(staticInEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'staticInEditor' and end of the first text node of 'innerEditor' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(outerEditor.textNode, 0,
+ innerEditor.textNode, innerEditor.textLength);
+ assert_equals(document.activeElement, outerEditor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'outerEditor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'outerEditor' and end of the first text node of 'innerEditor' (common editing host is outerEditor) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ staticAfter.textNode, staticAfter.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'staticAfter' (no common editing host) when active element is 'anchor'");
+test(function() {
+ resetFocusAndSelectionRange(anchor);
+ document.getSelection().setBaseAndExtent(innerEditor.textNode, 0,
+ anchor.textNode, anchor.textLength);
+ assert_equals(document.activeElement, anchor.element);
+ assert_equals(document.documentElement.scrollTop, 0);
+}, "Active element should be 'anchor' after Selection.setBaseAndExtent() with a range between start of the first text node of 'innerEditor' and end of the first text node of 'anchor' (no common editing host) when active element is 'anchor'");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html b/testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html
new file mode 100644
index 0000000000..35fd30f1f6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/delegateFocus-is-focusable.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus move tests caused by a call of Selection.addRange()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="host"></div>
+<script>
+let host = document.getElementById("host");
+let shadow = host.attachShadow({ mode: "open", delegatesFocus: true });
+shadow.innerHTML = `<button>Focusable</button>`;
+
+test(function() {
+ assert_true(SpecialPowers.Services.focus.elementIsFocusable(host, 0), "host is focusable");
+ host.focus();
+ assert_equals(document.activeElement, host, "Host is focused");
+ assert_equals(shadow.activeElement, shadow.querySelector("button"), "Button is focused");
+ assert_true(SpecialPowers.Services.focus.elementIsFocusable(host, 0), "host is still focusable");
+}, "isElementFocusable with delegateFocus");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html
new file mode 100644
index 0000000000..e765990145
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-different-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() before iframe loaded different site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-before-iframe-loaded-different-site-outer.sub.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html
new file mode 100644
index 0000000000..fd7c2bffa6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-before-iframe-loaded-same-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() before iframe loaded same site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-before-iframe-loaded-same-site-outer.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html
new file mode 100644
index 0000000000..58310fd687
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-different-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() from next tick before iframe loaded different site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html
new file mode 100644
index 0000000000..01b467718c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/focus-next-tick-before-iframe-loaded-same-site.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>focus() from next tick before iframe loaded same site</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+setup({explicit_done:true});
+window.onmessage = function(e) {
+ test(function() {
+ assert_equals(e.data, "PASS", "Check verdict");
+ }, "Check result");
+ w.close();
+ done();
+};
+var w = window.open("support/focus-next-tick-before-iframe-loaded-same-site-outer.html");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html b/testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html
new file mode 100644
index 0000000000..82a1346ec6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/iframe-focus-event-after-iframe-gets-focus.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test focus event after iframe gets focus</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function waitForMessage(target, checkFn) {
+ return new Promise(resolve => {
+ target.addEventListener("message", e => {
+ if (checkFn && !checkFn(e)) {
+ return;
+ }
+ resolve();
+ }, { once: true });
+ });
+}
+
+function start(w) {
+ w.postMessage("start", "*");
+}
+
+// This will send message to outer frame and also inner frame to ask them
+// send the log they collect back, the logs of outer and inner will be
+// concatenated.
+async function getLog(w) {
+ let log = "";
+ step_timeout(function() {
+ w.postMessage("getlog", "*");
+ }, 0);
+ await waitForMessage(window, (e) => {
+ log = e.data;
+ return true;
+ });
+ return log;
+}
+
+function runSingleTest(url, focusIframeFunction, expectedResult, description) {
+ promise_test(async t => {
+ let w = window.open(url);
+ t.add_cleanup(() => { w.close(); });
+ await waitForMessage(window, e => e.data === "ready");
+ start(w);
+ focusIframeFunction(w);
+ assert_equals(await getLog(w), expectedResult);
+ }, description);
+}
+
+function runTests(url, description) {
+ // Test calling iframe.focus();
+ runSingleTest(url, (w) => {
+ w.postMessage("iframefocus", "*");
+ }, "outerlog:windowblur,innerlog:windowfocus,",
+ description + " via calling iframe.focus()");
+
+ // Test calling iframe.contentWindow.focus();
+ runSingleTest(url, (w) => {
+ w.postMessage("iframecontentWindowfocus", "*");
+ }, "outerlog:windowblur,innerlog:windowfocus,",
+ description + " via calling iframe.contentWindow.focus()");
+
+ // Test calling window.focus() in iframe;
+ runSingleTest(url, (w) => {
+ w.postMessage("windowfocus", "*");
+ }, "outerlog:windowblur,innerlog:willfocuswindow,windowfocus,didfocuswindow,",
+ description + " via calling window.focus() in iframe");
+}
+
+// Test same site iframe
+runTests("support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html",
+ "Check iframe focus event after same site iframe gets focus");
+
+// Test different site iframe
+runTests("support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html",
+ "Check iframe focus event after different site iframe gets focus");
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html
new file mode 100644
index 0000000000..bcf23627d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() before iframe loaded different site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html
new file mode 100644
index 0000000000..e95fe7d292
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-different-site-outer.sub.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() before iframe loaded different site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/focus-before-iframe-loaded-different-site-inner.html";
+document.body.appendChild(iframe);
+iframe.focus();
+if (document.activeElement != iframe) {
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html
new file mode 100644
index 0000000000..3c277f078f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() before iframe loaded same site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html
new file mode 100644
index 0000000000..8c829d6d47
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-before-iframe-loaded-same-site-outer.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() before iframe loaded same site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "focus-before-iframe-loaded-same-site-inner.html";
+document.body.appendChild(iframe);
+iframe.focus();
+if (document.activeElement != iframe) {
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html
new file mode 100644
index 0000000000..2c1b35f2b2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() from next tick before iframe loaded different site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html
new file mode 100644
index 0000000000..83a48e303d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() from next tick before iframe loaded different site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "tick") {
+ iframe.focus();
+ if (document.activeElement != iframe) {
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+ }
+ return;
+ }
+
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html";
+document.body.appendChild(iframe);
+window.postMessage("tick", "*");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html
new file mode 100644
index 0000000000..62add75ed9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>focus() from next tick before iframe loaded same site</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ parent.postMessage("onload", "*");
+ }
+ document.body.onfocus = function() {
+ parent.postMessage("onfocus", "*");
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html
new file mode 100644
index 0000000000..a68d34039d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/focus-next-tick-before-iframe-loaded-same-site-outer.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>focus() from next tick before iframe loaded same site</title>
+</head>
+<body>
+<script>
+var pendingTimeout = false;
+window.onmessage = function(e) {
+ if (e.data == "tick") {
+ iframe.focus();
+ if (document.activeElement != iframe) {
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ opener.postMessage("FAIL activeElement", "*");
+ }
+ return;
+ }
+
+ if (e.data == "onload") {
+ if (pendingTimeout) {
+ return;
+ }
+ pendingTimeout = opener.step_timeout(function() {
+ opener.postMessage("FAIL missing onfocus", "*");
+ }, 2000);
+ return;
+ }
+ if (pendingTimeout) {
+ clearTimeout(pendingTimeout);
+ }
+ pendingTimeout = true;
+ if (e.data == "onfocus") {
+ // Test not upstreamed, because this even is a Firefoxism
+ // https://github.com/whatwg/html/issues/6209
+ opener.postMessage("PASS", "*");
+ return;
+ }
+ opener.postMessage("FAIL " + e.data, "*");
+}
+
+var iframe = document.createElement("iframe");
+iframe.src = "focus-next-tick-before-iframe-loaded-same-site-inner.html";
+document.body.appendChild(iframe);
+window.postMessage("tick", "*");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html
new file mode 100644
index 0000000000..d69580237c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-different-site-iframe-gets-focus-outer.sub.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iframe focus event after different site iframe gets focus outer</title>
+<iframe src="http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html"></iframe>
+<script>
+let outerlog = "outerlog:";
+
+let iframe = document.querySelector("iframe");
+window.onmessage = function(e) {
+ if (e.data == "start") {
+ window.onfocus = function() {
+ outerlog += "windowfocus,";
+ };
+ } else if (e.data == "iframefocus") {
+ iframe.focus();
+ } else if (e.data == "iframecontentWindowfocus") {
+ iframe.contentWindow.focus();
+ } else if (e.data == "windowfocus") {
+ iframe.contentWindow.postMessage("windowfocus", "*");
+ } else if (e.data == "getlog") {
+ iframe.contentWindow.postMessage("getlog", "*");
+ } else {
+ opener.postMessage(outerlog + e.data, "*");
+ }
+};
+
+window.onload = function() {
+ window.onblur = function() {
+ outerlog += "windowblur,";
+ };
+
+ iframe.onfocus = function() {
+ outerlog += "iframefocus,";
+ };
+
+ iframe.onblur = function() {
+ outerlog += "iframeblur,";
+ };
+
+ opener.postMessage("ready", "*");
+};
+</script>
diff --git a/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html
new file mode 100644
index 0000000000..64a360d248
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-iframe-gets-focus-inner.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Iframe focus event inner document</title>
+</head>
+<body>
+<h1>Inner</h1>
+<script>
+let innerlog = "innerlog:";
+
+window.onmessage = function(e) {
+ if (e.data == "windowfocus") {
+ innerlog += "willfocuswindow,";
+ window.focus();
+ innerlog += "didfocuswindow,";
+ } else if (e.data == "getlog") {
+ parent.postMessage(innerlog, "*");
+ }
+};
+
+window.onfocus = function() {
+ innerlog += "windowfocus,";
+};
+
+window.onblur = function() {
+ innerlog += "windowblur,";
+};
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html
new file mode 100644
index 0000000000..06040d6485
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/focus/support/iframe-focus-event-after-same-site-iframe-gets-focus-outer.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Iframe focus event after same site iframe gets focus outer</title>
+<iframe src="iframe-focus-event-after-iframe-gets-focus-inner.html"></iframe>
+<script>
+let outerlog = "outerlog:";
+
+let iframe = document.querySelector("iframe");
+window.onmessage = function(e) {
+ if (e.data == "start") {
+ window.onfocus = function() {
+ outerlog += "windowfocus,";
+ };
+ } else if (e.data == "iframefocus") {
+ iframe.focus();
+ } else if (e.data == "iframecontentWindowfocus") {
+ iframe.contentWindow.focus();
+ } else if (e.data == "windowfocus") {
+ iframe.contentWindow.postMessage("windowfocus", "*");
+ } else if (e.data == "getlog") {
+ iframe.contentWindow.postMessage("getlog", "*");
+ } else {
+ opener.postMessage(outerlog + e.data, "*");
+ }
+};
+
+window.onload = function() {
+ window.onblur = function() {
+ outerlog += "windowblur,";
+ };
+
+ iframe.onfocus = function() {
+ outerlog += "iframefocus,";
+ };
+
+ iframe.onblur = function() {
+ outerlog += "iframeblur,";
+ };
+
+ opener.postMessage("ready", "*");
+};
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/navigating-across-documents/location-hash.sub.html b/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/navigating-across-documents/location-hash.sub.html
new file mode 100644
index 0000000000..7590799e1c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/navigating-across-documents/location-hash.sub.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+setup({ single_test: true });
+
+let CROSS_ORIGIN_HOST = "{{hosts[alt][]}}";
+let sameOriginLocation1;
+let crossOriginLocation;
+let sameOriginLocation2;
+
+// Load first a same origin page to an iframe and store its location, then
+// do the same for a cross origin page and then again for a same origin page.
+// Check whether accessing .hash works and what the value is.
+// Then remove the iframe and check .hash accesses again.
+
+function runTest() {
+ let ifr = document.createElement("iframe");
+ ifr.src = "resources/blank.html";
+ ifr.onload = function() {
+ sameOriginLocation1 = ifr.contentWindow.location;
+ let initialHref = sameOriginLocation1.href;
+ assert_equals(sameOriginLocation1.hash, "");
+ sameOriginLocation1.hash = "1";
+ assert_equals(sameOriginLocation1.hash, "#1");
+ let exceptionConstructor = ifr.contentWindow.DOMException;
+ ifr.onload = function() {
+ crossOriginLocation = ifr.contentWindow.location;
+ assert_throws_dom("SecurityError", () => crossOriginLocation.hash,
+ "Accessing cross origin location.hash should throw");
+ assert_throws_dom("SecurityError", exceptionConstructor, () => sameOriginLocation1.hash,
+ "Accessing cross origin location.hash should throw");
+
+ crossOriginLocation.href = initialHref;
+ ifr.onload = function() {
+ sameOriginLocation2 = ifr.contentWindow.location;
+ assert_not_equals(sameOriginLocation1, sameOriginLocation2);
+ assert_equals(sameOriginLocation1.hash, "");
+ assert_equals(sameOriginLocation2.hash, "");
+ assert_throws_dom("SecurityError", () => crossOriginLocation.hash,
+ "Accessing cross origin location.hash should throw");
+ sameOriginLocation2.hash = "2";
+ assert_equals(sameOriginLocation2.hash, "#2");
+ assert_equals(sameOriginLocation1.hash, "#2");
+
+ ifr.remove();
+ assert_throws_dom("SecurityError", () => crossOriginLocation.hash,
+ "Accessing cross origin location.hash should throw");
+ assert_equals(sameOriginLocation2.hash, "");
+ assert_equals(sameOriginLocation1.hash, "");
+ done();
+ }
+ }
+ sameOriginLocation1.host = CROSS_ORIGIN_HOST;
+ }
+ document.body.appendChild(ifr);
+}
+
+window.onload = function() {
+ setTimeout(runTest);
+}
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html b/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html
new file mode 100644
index 0000000000..4c58514e66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/browsers/browsing-the-web/read-media/sandboxed-video.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Test load of media document in sandboxed iframe</title>
+<link rel="motivation" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783601">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body></body>
+<script>
+promise_test(async () => {
+ const frame = document.createElement('iframe');
+ frame.sandbox = '';
+ frame.src =
+ // 'PartialContent' ensures that the entire video resource does not load
+ // in one fetch.
+ '/service-workers/service-worker/resources/fetch-access-control.py?'
+ + 'VIDEO&PartialContent';
+
+ document.body.appendChild(frame);
+ await new Promise(resolve => frame.onload = resolve);
+
+ const video = SpecialPowers.wrap(frame).contentDocument.body.childNodes[0];
+ video.muted = true; // to allow playback
+ return video.play();
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html
new file mode 100644
index 0000000000..ed59c3ae99
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-01.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to a slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<input type=range list=tickmarks min=-5 max=35>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ const step = 1;
+ promise_test(async function snapToTickMarks() {
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const left = rect.left + padding;
+ const width = rect.width - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (left + width * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(moveTo, rect.top)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+ promise_test(async function domDoesNotSnap() {
+ const startAt = -2;
+ range.value = startAt;
+ for (let expectedValue = startAt + 1; expectedValue <= 6; expectedValue++) {
+ range.stepUp();
+ assert_equals(parseInt(range.value), expectedValue);
+ }
+ });
+ promise_test(async function keyboardDoesNotSnap() {
+ const kArrowRight = "\uE014";
+ range.focus();
+ const startAt = -2;
+ range.value = startAt;
+ for (let expectedValue = startAt + 1; expectedValue <= 6; expectedValue++) {
+ await test_driver.send_keys(range, kArrowRight);
+ assert_equals(parseInt(range.value), expectedValue);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html
new file mode 100644
index 0000000000..061f34b3a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-02.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to an RTL slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<input type=range list=tickmarks min=-5 max=35 dir=rtl>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ const step = 1;
+ promise_test(async function snapToRTLTickMarks() {
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const right = rect.right - padding;
+ const width = rect.width - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (right - width * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(moveTo, rect.top)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html
new file mode 100644
index 0000000000..9397c39e0a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-03.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to a vertical slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<style>
+ input[type=range] {
+ writing-mode: vertical-lr;
+ direction: rtl; /* so the range progresses upwards, not downwards */
+ }
+</style>
+<input type=range list=tickmarks min=-5 max=35>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ promise_test(async function snapToVerticalTickMarks() {
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const bottom = rect.bottom - padding;
+ const height = rect.height - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (bottom - height * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(rect.left, moveTo)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-04.html b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-04.html
new file mode 100644
index 0000000000..b6d32a82c4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/rendering/non-replaced-elements/form-controls/range-snap-to-tick-marks-04.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name=viewport content=width=device-width>
+<title>Snap to a vertical slider's tick marks by clicking near them</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/rendering.html#the-input-element-as-a-range-control">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803118">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<style>
+ input[type=range] {
+ writing-mode: vertical-lr;
+ }
+</style>
+<input type=range list=tickmarks min=-5 max=35>
+<datalist id=tickmarks>
+ <option value=0></option>
+ <option value=3></option>
+</datalist>
+<script>
+ const range = document.querySelector("input[type=range]");
+ promise_test(async function snapToVerticalTickMarks() {
+ // Same assertions as range-snap-to-tick-marks-03, but without direction:rtl the range
+ // will progress downwards instead of upwards, so the coord calculation is reversed
+ // in the y-direction.
+ const assertions = [[-3, "-3"], [-2, "0"], [1, "0"], [2, "3"], [5, "3"], [6, "6"]];
+ const rect = range.getBoundingClientRect();
+ const padding = 10;
+ const top = rect.top + padding;
+ const height = rect.height - 2 * padding;
+ const actions = new test_driver.Actions();
+ const min = parseInt(range.min);
+ const max = parseInt(range.max);
+ for (const assertion of assertions) {
+ const moveTo = (top + height * (assertion[0] - min) / (max - min)) | 0;
+ const expected = assertion[1];
+ await actions
+ .pointerMove(rect.left, moveTo)
+ .pointerDown()
+ .pointerUp()
+ .send();
+ assert_equals(range.value, expected);
+ }
+ });
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js b/testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js
new file mode 100644
index 0000000000..9b5aa88abb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/form-submission-0/non-usv-filenames.window.js
@@ -0,0 +1,95 @@
+// META: script=/html/semantics/forms/form-submission-0/enctypes-helper.js
+
+// This test is built on the same infrastructure as the WPT tests
+// urlencoded2.window.js, multipart-formdata.window.js and text-plain.window.js,
+// except modified because this file only tests the serialization of filenames.
+// See the enctypes-helper.js file in the regular WPT test suite for more info.
+
+// The `urlencoded`, `multipart` and `textPlain` functions take a `file`
+// property rather than `name` and `value` properties, and the value of
+// `expected` is the serialization of the filename in the given encoding.
+
+function formSubmissionTemplate2(enctype, expectedBuilder) {
+ const formTestFn = formSubmissionTemplate(enctype, expectedBuilder);
+ return ({ file, formEncoding, expected, description }) =>
+ formTestFn({ name: "a", value: file, formEncoding, expected, description });
+}
+
+const urlencoded = formSubmissionTemplate2(
+ "application/x-www-form-urlencoded",
+ filename => `a=${filename}`
+);
+const multipart = formSubmissionTemplate2(
+ "multipart/form-data",
+ (filename, serialized) => {
+ const boundary = serialized.split("\r\n")[0];
+ return [
+ boundary,
+ `Content-Disposition: form-data; name="a"; filename="${filename}"`,
+ "Content-Type: text/plain",
+ "",
+ "", // File contents
+ `${boundary}--`,
+ "",
+ ].join("\r\n");
+ }
+);
+const textPlain = formSubmissionTemplate2(
+ "text/plain",
+ filename => `a=${filename}\r\n`
+);
+
+// -----------------------------------------------------------------------------
+
+(async () => {
+ // This creates an empty filesystem file with an arbitrary name and returns it
+ // as a File object with name "a\uD800b".
+ const file = SpecialPowers.unwrap(
+ await SpecialPowers.createFiles(
+ [{ data: "", options: { name: "a\uD800b", type: "text/plain" } }],
+ files => files[0]
+ )
+ );
+
+ urlencoded({
+ file,
+ formEncoding: "UTF-8",
+ expected: "a%EF%BF%BDb",
+ description: "lone surrogate in filename, UTF-8",
+ });
+
+ urlencoded({
+ file,
+ formEncoding: "windows-1252",
+ expected: "a%26%2365533%3Bb",
+ description: "lone surrogate in filename, windows-1252",
+ });
+
+ multipart({
+ file,
+ formEncoding: "UTF-8",
+ expected: "a\xEF\xBF\xBDb",
+ description: "lone surrogate in filename, UTF-8",
+ });
+
+ multipart({
+ file,
+ formEncoding: "windows-1252",
+ expected: "a&#65533;b",
+ description: "lone surrogate in filename, windows-1252",
+ });
+
+ textPlain({
+ file,
+ formEncoding: "UTF-8",
+ expected: "a\xEF\xBF\xBDb",
+ description: "lone surrogate in filename, UTF-8",
+ });
+
+ textPlain({
+ file,
+ formEncoding: "windows-1252",
+ expected: "a&#65533;b",
+ description: "lone surrogate in filename, windows-1252",
+ });
+})();
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html b/testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html
new file mode 100644
index 0000000000..2eee99ffaf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/input-radio-key-navigation.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<title>Keyboard navigation on input type=radio</title>
+<meta charset=utf-8>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<form dir="ltr">
+ <input type=radio name=whatever value=1>
+ <input type=radio name=whatever value=2>
+ <input type=radio name=whatever value=3>
+</form>
+<form dir="rtl">
+ <input type=radio name=whatever value=1>
+ <input type=radio name=whatever value=2>
+ <input type=radio name=whatever value=3>
+</form>
+<script>
+const KEYS = {
+ ArrowLeft: '\uE012',
+ ArrowUp: '\uE013',
+ ArrowRight: '\uE014',
+ ArrowDown: '\uE015',
+};
+
+function nextFocusIndex(currentIndex, length, forward) {
+ if (forward) {
+ return (currentIndex + 1) % length;
+ }
+ return (currentIndex == 0 ? length : currentIndex) - 1;
+}
+
+async function testMove(form, keyName, forward) {
+ let radios = form.querySelectorAll("input[type=radio]");
+ assert_equals(radios.length, 3, "Sanity check");
+
+ let focusIndex = 1;
+ radios[focusIndex].focus();
+
+ // Enough to wrap around, and one more to test the last active element too.
+ for (let i = 0; i <= radios.length; ++i) {
+ assert_equals(document.activeElement, radios[focusIndex], `Focused expected radio input (${focusIndex})`);
+ await test_driver.send_keys(document.activeElement, KEYS[keyName]);
+ focusIndex = nextFocusIndex(focusIndex, radios.length, forward);
+ }
+}
+
+promise_test(async t => {
+ for (let form of document.querySelectorAll("form")) {
+ const rtl = form.dir == "rtl";
+ await testMove(form, "ArrowDown", /* forward = */ true);
+ await testMove(form, "ArrowUp", /* forward = */ false);
+ await testMove(form, "ArrowLeft", /* forward = */ rtl);
+ await testMove(form, "ArrowRight", /* forward = */ !rtl);
+ }
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html b/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
new file mode 100644
index 0000000000..c6ba07f746
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/textfieldselection/selection-value-interactions.html
@@ -0,0 +1,217 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<div id=target></div>
+<script>
+ var target = document.getElementById("target");
+ var sometext = "something";
+ var shorttext = "abc";
+ var elemData = [
+ {
+ desc: "textarea not in body",
+ factory: () => document.createElement("textarea"),
+ },
+ {
+ desc: "input not in body",
+ factory: () => document.createElement("input"),
+ },
+ {
+ desc: "textarea in body",
+ factory: () => document.body.appendChild(document.createElement("textarea")),
+ },
+ {
+ desc: "input in body",
+ factory: () => document.body.appendChild(document.createElement("input")),
+ },
+ {
+ desc: "textarea in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<textarea>abcdefghij</textarea>"
+ return target.querySelector("textarea");
+ },
+ },
+ {
+ desc: "input in body with parsed default value",
+ factory: () => {
+ target.innerHTML = "<input value='abcdefghij'>"
+ return target.querySelector("input");
+ },
+ },
+ {
+ desc: "focused textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ return t;
+ },
+ },
+ {
+ desc: "focused input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ return i;
+ },
+ },
+ {
+ desc: "focused then blurred textarea",
+ factory: () => {
+ var t = document.body.appendChild(document.createElement("textarea"));
+ t.focus();
+ t.blur();
+ return t;
+ },
+ },
+ {
+ desc: "focused then blurred input",
+ factory: () => {
+ var i = document.body.appendChild(document.createElement("input"));
+ i.focus();
+ i.blur()
+ return i;
+ },
+ },
+ ];
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ assert_equals(el.selectionStart, 0,
+ `Cursor start should be at beginning of ${data.desc}`);
+ assert_equals(el.selectionEnd, 0,
+ `Cursor end should be at beginning of ${data.desc}`);
+ }, `cursor location for initial value of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.defaultValue = sometext;
+ // The "focused or has been focused" case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ assert_equals(el.selectionStart, 0,
+ `Cursor start should be at beginning of ${data.desc}`);
+ assert_equals(el.selectionEnd, 0,
+ `Cursor end should be at beginning of ${data.desc}`);
+ }
+ }, `cursor location after defaultValue set of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.selectionStart = el.selectionStart;
+ el.defaultValue = sometext;
+ // The focused case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ assert_equals(el.selectionStart, 0,
+ `Cursor start should be at beginning of ${data.desc}`);
+ assert_equals(el.selectionEnd, 0,
+ `Cursor end should be at beginning of ${data.desc}`);
+ }
+ }, `cursor location after defaultValue set after no-op selectionStart set of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ el.value = sometext;
+ assert_equals(el.selectionStart, sometext.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, sometext.length,
+ `Cursor end should be at end of ${data.desc}`);
+ }, `cursor location after value set of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ assert_true(sometext.length > 8,
+ "sometext too short, test won't work right");
+ el.defaultValue = sometext;
+ el.selectionStart = 1;
+ el.selectionEnd = 8;
+ assert_equals(el.selectionStart, 1, "We just set selectionStart!");
+ assert_equals(el.selectionEnd, 8, "We just set selectionEnd!");
+ assert_true(shorttext.length > 1,
+ "shorttext too short, test won't work right");
+ assert_true(shorttext.length < 8,
+ "shorttext too long, test won't work right");
+ el.defaultValue = shorttext;
+ // The "focused or has been focused" case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ if (el.tagName.toLowerCase() === "textarea") {
+ assert_equals(el.selectionStart, 0,
+ "Selection should be collapsed to the beginning");
+ assert_equals(el.selectionEnd, 0,
+ "Selection should be collapsed to the beginning");
+ } else {
+ assert_equals(el.selectionStart, 1,
+ "Shouldn't have moved selection start");
+ assert_equals(el.selectionEnd, shorttext.length,
+ "Should have adjusted selection end");
+ }
+ }
+ }, `selection location after defaultValue set to shorter than selectionEnd of ${data.desc}`);
+}
+
+for (var data of elemData) {
+ test(function() {
+ var el = data.factory();
+ this.add_cleanup(() => el.remove());
+ assert_true(sometext.length > 8,
+ "sometext too short, test won't work right");
+ el.defaultValue = sometext;
+ el.selectionStart = 5;
+ el.selectionEnd = 8;
+ assert_equals(el.selectionStart, 5, "We just set selectionStart!");
+ assert_equals(el.selectionEnd, 8, "We just set selectionEnd!");
+ assert_true(shorttext.length < 5,
+ "shorttext too long, test won't work right");
+ el.defaultValue = shorttext;
+ // The "focused or has been focused" case behaves differently.
+ if (data.desc.includes("focused")) {
+ assert_equals(el.selectionStart, el.value.length,
+ `Cursor start should be at end of ${data.desc}`);
+ assert_equals(el.selectionEnd, el.value.length,
+ `Cursor end should be at end of ${data.desc}`);
+ } else {
+ if (el.tagName.toLowerCase() === "textarea") {
+ assert_equals(el.selectionStart,0,
+ "Selection should be collapsed to the beginning");
+ assert_equals(el.selectionEnd, 0,
+ "Selection should be collapsed to the beginning");
+ } else {
+ assert_equals(el.selectionStart, shorttext.length,
+ "Should have adjusted selection start");
+ assert_equals(el.selectionEnd, shorttext.length,
+ "Should have adjusted selection end");
+ }
+ }
+ }, `selection location after defaultValue set to shorter than selectionStart of ${data.desc}`);
+}
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/input-activation-behavior.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/input-activation-behavior.html
new file mode 100644
index 0000000000..d0ff8fd666
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/input-activation-behavior.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Activation behavior of input</title>
+<link rel="help" href="https://dom.spec.whatwg.org/#eventtarget-activation-behavior">
+<link rel="help" href="https://dom.spec.whatwg.org/#concept-event-dispatch">
+<link rel="help" href="https://html.spec.whatwg.org/#the-input-element">
+<link rel="help" href="https://github.com/whatwg/html/issues/1568">
+<link rel="help" href="https://github.com/whatwg/html/issues/1576">
+<link rel="help" href="https://github.com/whatwg/html/issues/10032">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+
+<div id=test_container>
+ <a href="javascript:activated(document.querySelector('a'))" class="target"></a>
+ <area href="javascript:activated(document.querySelector('area'))" class="target">
+</div>
+
+<script>
+let activations = [];
+function activated(e) {
+ activations.push(e);
+}
+
+function testActivation(inputType, hasFormOwner, followTheParentLink) {
+ const elements = document.getElementsByClassName("target");
+ for (const anchor of elements) {
+ promise_test(async t => {
+ const input = document.createElement("input");
+ input.type = inputType;
+ input.oninput = function (e) {
+ activated(this);
+ };
+
+ if (hasFormOwner) {
+ const form = document.createElement("form");
+ form.onsubmit = function (e) {
+ activated(this.firstElementChild);
+ e.preventDefault();
+ return false;
+ };
+ form.onreset = function (e) {
+ activated(this.firstElementChild);
+ };
+ form.appendChild(input);
+ anchor.appendChild(form);
+ t.add_cleanup(function() {
+ form.remove();
+ activations = [];
+ });
+ } else {
+ anchor.appendChild(input);
+ t.add_cleanup(function() {
+ input.remove();
+ activations = [];
+ });
+ }
+
+ input.click();
+
+ // This is for a/area where JavaScript is executed in a queued task.
+ await new Promise(resolve => {
+ t.step_timeout(() => {
+ t.step_timeout(() => {
+ // All browser doesn't follow the spec for input button, see
+ // https://github.com/whatwg/html/issues/1576.
+ assert_array_equals(activations, [followTheParentLink ? anchor : input]);
+ if (inputType == "checkbox" || inputType == "radio") {
+ assert_equals(input.checked, true, "check input.checked");
+ }
+ resolve();
+ }, 0);
+ }, 0);
+ });
+ }, `Click child input ${inputType} ${hasFormOwner ? "with" : "without"} form owner ` +
+ `of parent ${anchor.tagName}, activation target should be ${followTheParentLink ? anchor.tagName : "input"}`);
+ }
+}
+
+// Click input types without form owner should not follow the parent link.
+const TypesWithoutFormOwnerNotFollowParentLink = ["checkbox", "radio"];
+for (const type of TypesWithoutFormOwnerNotFollowParentLink) {
+ testActivation(type, false /* hasFormOwner */, false /* followTheParentLink */);
+}
+
+// Click input types without form owner should follow the parent link.
+const TypesWithoutFormOwnerFollowParentLink = ["button", "reset", "submit"];
+for (const type of TypesWithoutFormOwnerFollowParentLink) {
+ testActivation(type, false /* hasFormOwner */, true /* followTheParentLink */);
+}
+
+// Click input types with form owner should not follow the parent link.
+const TypesWithFormOwnerNotFollowParentLink = ["submit", "reset", "checkbox", "radio"];
+for (const type of TypesWithFormOwnerNotFollowParentLink) {
+ testActivation(type, true /* hasFormOwner */, false /* followTheParentLink */);
+}
+
+// Click input types with form owner should follow the parent link.
+const TypesWithFormOwnerFollowParentLink = ["button"];
+for (const type of TypesWithFormOwnerFollowParentLink) {
+ testActivation(type, true /* hasFormOwner */, true /* followTheParentLink */);
+}
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html
new file mode 100644
index 0000000000..67591468cf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01-notref.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>vertical range input with datalist reference</title>
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html
new file mode 100644
index 0000000000..f1bd96f391
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-01.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>vertical range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="mismatch" href="range-tick-marks-01-notref.html">
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html
new file mode 100644
index 0000000000..59acde1482
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02-notref.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<title>max and min attributes applied to vertical range input with datalist reference</title>
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html
new file mode 100644
index 0000000000..bd45631d4a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-02.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>max and min attributes applied to vertical range input with datalist</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="mismatch" href="range-tick-marks-02-notref.html">
+<input type="range" orient="vertical" min="-40" max="40" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0">
+ <option value="-30">
+ <option value="30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html
new file mode 100644
index 0000000000..df473920ec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>no vertical range tick marks for disabled datalist elements reference</title>
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="-30">
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html
new file mode 100644
index 0000000000..83b5c2eb66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-03.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>no vertical range tick marks for disabled datalist elements</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#htmldatalistelement">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-disabled">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=841942">
+<link rel="author" href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel="match" href="range-tick-marks-03-ref.html">
+<input type="range" orient="vertical" min="-100" max="100" value="0" step="10" name="power" list="powers">
+<datalist id="powers">
+ <option value="0" disabled>
+ <option value="-30">
+ <option value="30" disabled>
+ <option value="50">
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html
new file mode 100644
index 0000000000..c2bf59e52b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04-ref.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<title>no range tick marks for vertical range tick marks that are step mismatches reference</title>
+<input type=range step=3 value=1 min=-5 max=5 list=degrees>
+<datalist id=degrees>
+ <option value=-2>
+ <option value=4>
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html
new file mode 100644
index 0000000000..a47334b411
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/the-input-element/range-tick-marks-04.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>no range tick marks for vertical range tick marks that are step mismatches</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range)">
+<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1803303">
+<link rel=author href="mailto:zach@zrhoffman.net" title="Zach Hoffman">
+<link rel=match href=range-tick-marks-04-ref.html>
+<input type=range step=3 value=1 min=-5 max=5 list=degrees>
+<datalist id=degrees>
+ <option value=-4>
+ <option value=-2>
+ <option value=0>
+ <option value=2>
+ <option value=4>
+</datalist>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html b/testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html
new file mode 100644
index 0000000000..2ffeb22cb4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/forms/time-enter-keypress.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<title>Enter submits on time input</title>
+<meta charset=utf-8>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<form>
+ <input type="time" name="time">
+ <input type=submit>
+</form>
+<form>
+ <input type="date" name="date">
+ <input type=submit>
+</form>
+<form>
+ <input type="datetime-local" name="datetime-local">
+ <input type=submit>
+</form>
+<script>
+async function testEnterOnInput(form) {
+ const submitted = new Promise(resolve => {
+ form.addEventListener("submit", function(e) {
+ e.preventDefault();
+ resolve();
+ }, { once: true });
+ });
+ const input = form.querySelector("input");
+ input.focus();
+
+ const ENTER = "\uE007";
+ await new test_driver.Actions()
+ .keyDown(ENTER)
+ .keyUp(ENTER)
+ .send()
+ await submitted;
+ assert_true(true, "Form was submitted on enter for input " + input.type);
+}
+
+promise_test(async t => {
+ for (let form of document.querySelectorAll("form")) {
+ await testEnterOnInput(form, t);
+ }
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html
new file mode 100644
index 0000000000..c99893a786
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-circular.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules circular</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async dynamic module")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_dynamic_module_circular.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html
new file mode 100644
index 0000000000..ff2ac06222
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module-error.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules which errors</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load dynamically imported async modules with error")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_dynamic_module_error.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html
new file mode 100644
index 0000000000..2e1b267492
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-dynamic-module.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async dynamic module")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_dynamic_module.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html
new file mode 100644
index 0000000000..7540ebb5ec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-circular.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load async modules circular</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async module")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ test.done();
+ }
+ function scriptLoaded() {
+ test.step(() => assert_unreached("Should not load before error"));
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_module_circular.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()">
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html
new file mode 100644
index 0000000000..c720611080
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module-error.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load an async module</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load dynamic async module with syntax error")
+ window.addEventListener("error", scriptError);
+ function scriptError(e) {
+ // An error is expected
+ test.step(() => assert_true(e.message !== "FAIL"));
+ }
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_module_error.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html
new file mode 100644
index 0000000000..566e2a379d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/async-module.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load dynamically imported async modules</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load async dynamic module")
+ function scriptLoaded() {
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/async_module.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()">
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html
new file mode 100644
index 0000000000..e472656e34
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/circular-module-import-with-syntax-error.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Load a module with circular imports and syntax error</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Load module with circular imports and syntax error")
+ window.addEventListener("error", scriptError);
+ function scriptError() {
+ // An error is expected
+ test.done();
+ }
+ function scriptLoaded() {
+ test.step(() => assert_unreached("Should not load"));
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ src="./support/circular_error1.js"
+ onerror="testNoError()"
+ onload="scriptLoaded()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html
new file mode 100644
index 0000000000..44337a0217
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/create-module-script.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Insert non-async module script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var test = async_test("Create module script")
+ var moduleRan = false;
+ function loadModule() {
+ var script = document.createElement("script");
+ script.onerror = function() {
+ test.step(() => assert_unreached("Should not get an error"));
+ test.done();
+ };
+ script.onload = function() {
+ test.step(() => assert_equals(moduleRan, true));
+ test.done();
+ };
+ script.type = "module";
+ script.src = "support/module.js";
+ script.async = false;
+ document.documentElement.appendChild(script);
+ }
+</script>
+<body onload='loadModule()'></body>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/inline-module-order.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/inline-module-order.html
new file mode 100644
index 0000000000..3df7624187
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/inline-module-order.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Execution order of non-parser created inline module scripts</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<!-- Ensure imported module is already in the module map -->
+<script type="module" src="./support/empty_module.js"></script>
+
+<div id="parent"></div>
+
+<script>
+ let result = [];
+
+ function createInlineModuleScript(source) {
+ let parent = document.getElementById("parent");
+ let script = document.createElement("script");
+ script.type = "module";
+ script.textContent = source;
+ parent.appendChild(script);
+ }
+
+ promise_test(async test => {
+ await new Promise(resolve => window.onload = resolve);
+
+ createInlineModuleScript(`
+ import {} from "./support/empty_module.js";
+ result.push(1);
+ `);
+
+ createInlineModuleScript(`
+ result.push(2);
+ `);
+
+ await test.step_wait(() => result.length == 2, "Wait for both scripts to be executed", 1000);
+ test.step(() => assert_array_equals(result, [2, 1],
+ "Check expected execution order"));
+ test.done();
+ }, "Execution order of non-parser created inline module scripts");
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html
new file mode 100644
index 0000000000..5342bd525c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/mixed-content-import.https.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Attempt to load a mixed content module graph</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+ var test = async_test("Attempt to load a mixed content module graph")
+ window.addEventListener("error", testNoError);
+ function scriptError() {
+ // An error is expected
+ test.done();
+ }
+ function scriptLoaded() {
+ test.step(() => assert_unreached("Should not load"));
+ test.done();
+ }
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+</script>
+<script type="module"
+ onerror="scriptError()"
+ onload="scriptLoaded()"
+ src="./support/mixed_import.js">
+</script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html
new file mode 100644
index 0000000000..5d059b898a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/module-error-reporting.html
@@ -0,0 +1,89 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Insert non-async module script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ const test = async_test("Test error event properties");
+
+ let errorCount = 0;
+ let eventCount = 0;
+ window.addEventListener("error", handleError);
+ window.addEventListener("load", handleLoaded);
+
+ function handleEvent(event) {
+ eventCount++;
+ test.step(() => assert_equals(typeof event, "object"));
+ test.step(() => assert_true(event instanceof Event));
+ }
+
+ function handleError(event) {
+ errorCount++;
+ test.step(() => assert_equals(typeof event, "object"));
+ test.step(() => assert_true(event instanceof ErrorEvent));
+ switch (errorCount) {
+ case 1:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/bad_local_export.js")));
+ test.step(() => assert_equals(event.lineno, 3));
+ test.step(() => assert_equals(event.colno, 8));
+ break;
+ case 2:
+ test.step(() => assert_true(event.error instanceof TypeError));
+ test.step(() => assert_true(event.filename.endsWith("/import_resolve_failure.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 17));
+ break;
+ case 3:
+ test.step(() => assert_true(event.error instanceof TypeError));
+ test.step(() => assert_true(event.filename.endsWith("/indirect_export_resolve_failure.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 20));
+ break;
+ case 4:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/missing_import.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 9));
+ break;
+ case 5:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/missing_indirect_export.js")));
+ test.step(() => assert_equals(event.lineno, 2));
+ test.step(() => assert_equals(event.colno, 12));
+ break;
+ case 6:
+ test.step(() => assert_true(event.error instanceof SyntaxError));
+ test.step(() => assert_true(event.filename.endsWith("/module_eval_error.js")));
+ test.step(() => assert_equals(event.lineno, 3));
+ test.step(() => assert_equals(event.colno, 1));
+ break;
+ }
+ }
+
+ function testNoError() {
+ test.step(() => assert_unreached("No event expected here"));
+ test.done();
+ }
+
+ function handleLoaded() {
+ test.step(() => assert_equals(eventCount, 2));
+ test.step(() => assert_equals(errorCount, 6));
+ test.done();
+ }
+
+</script>
+
+<!-- Errors that fire an event on the script element -->
+<script type="module" src="" onerror="handleEvent(event)"></script>
+<script type="module" src="./does_not_exist" onerror="handleEvent(event)"></script>
+
+<!-- Errors that fire an error event on the global -->
+<script type="module" src="./support/bad_local_export.js" onerror="handleEvent(event)"></script>
+<script type="module" src="./support/import_resolve_failure.js" onerror="testNoError()"></script>
+<script type="module" src="./support/indirect_export_resolve_failure.js" onerror="testNoError()"></script>
+<script type="module" src="./support/missing_import.js" onerror="testNoError()"></script>
+<script type="module" src="./support/missing_indirect_export.js" --onerror="testNoError()"></script>
+<script type="module" src="./support/module_eval_error.js" onerror="testNoError()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html
new file mode 100644
index 0000000000..b95d3fe330
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/reload-failed-module-script.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Insert non-async module script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ setup({allow_uncaught_exception: true});
+
+ var test = async_test("Reload failed module script");
+
+ var errorCount = 0;
+ window.addEventListener("error", handleError);
+
+ function handleError() {
+ errorCount++;
+
+ if (errorCount == 1) {
+ reloadModule();
+ return;
+ }
+
+ test.step(() => assert_equals(errorCount, 2));
+ test.done();
+ }
+
+ function reloadModule() {
+ var script = document.createElement("script");
+ script.onerror = testNoError;
+ script.type = "module";
+ script.src = "support/missing_import.js";
+ script.async = false;
+ document.documentElement.appendChild(script);
+ }
+
+ function testNoError() {
+ test.step(() => assert_unreached("No event expect here"));
+ test.done();
+ }
+
+</script>
+<script type="module" src="support/missing_import.js" onerror="testNoError()"></script>
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js
new file mode 100644
index 0000000000..238dc11402
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module.js
@@ -0,0 +1,11 @@
+var ns = await import('./async_test_module.js');
+if (ns.default !== 42) {
+ throw new Error("FAIL");
+}
+if (ns.x !== "named") {
+ throw new Error("FAIL");
+}
+if (ns.y !== 39) {
+ throw new Error("FAIL");
+}
+
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js
new file mode 100644
index 0000000000..93bfc3aca8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_circular.js
@@ -0,0 +1,5 @@
+try {
+ var ns = await import('./async_test_module_circular_1.js');
+} catch(ns) {
+ throw Error("Fails as expected");
+};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js
new file mode 100644
index 0000000000..3832960108
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_dynamic_module_error.js
@@ -0,0 +1,5 @@
+try {
+ await import('./bad_local_export.js');
+} catch(ns) {
+ throw Error("Fails as expected");
+};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js
new file mode 100644
index 0000000000..34a590bcfc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module.js
@@ -0,0 +1,14 @@
+import nsPromise from './async_test_module.js';
+
+console.log("hi");
+let ns = await nsPromise;
+
+if (ns.default !== 42) {
+ throw new Error("FAIL");
+}
+if (ns.x !== "named") {
+ throw new Error("FAIL");
+}
+if (ns.y !== 39) {
+ throw new Error("FAIL");
+}
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js
new file mode 100644
index 0000000000..a9dff71b17
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_circular.js
@@ -0,0 +1,3 @@
+import module from './async_test_module_circular_1.js';
+
+throw new Error("FAIL");
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js
new file mode 100644
index 0000000000..3fa6991768
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_module_error.js
@@ -0,0 +1,4 @@
+import ns from "./async_test_module_failure.js";
+
+throw Error("FAIL");
+
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js
new file mode 100644
index 0000000000..201c76eedf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module.js
@@ -0,0 +1,12 @@
+await 1;
+await 2;
+export default await Promise.resolve(42);
+
+export const y = await 39;
+export const x = await 'named';
+
+// Bonus: this rejection is not unwrapped
+if (false) {
+ await Promise.reject(42);
+}
+
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js
new file mode 100644
index 0000000000..2fdf67baca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_1.js
@@ -0,0 +1,3 @@
+import module from './async_test_module_circular_2.js';
+
+export default {};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js
new file mode 100644
index 0000000000..0a09aacc39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_2.js
@@ -0,0 +1,5 @@
+import module from './async_test_module_circular_3.js';
+
+await module.test();
+
+export default {};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js
new file mode 100644
index 0000000000..d815bc00e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_circular_3.js
@@ -0,0 +1,8 @@
+import module from './async_test_module_circular_1.js';
+
+export default {
+ async test() {
+ throw new Error("error thrown");
+ return Promise.resolve()
+ }
+};
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js
new file mode 100644
index 0000000000..6f823f3003
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/async_test_module_failure.js
@@ -0,0 +1,6 @@
+export default 42;
+
+export const named = 'named';
+
+var rejection = Promise.reject(TypeError('I reject this!'));
+await rejection;
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js
new file mode 100644
index 0000000000..0b3df8de32
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/bad_local_export.js
@@ -0,0 +1,3 @@
+// Attempt to export something that doesn't exist.
+
+export missing;
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js
new file mode 100644
index 0000000000..f0310fe0b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error1.js
@@ -0,0 +1,2 @@
+import { test2 } from "./circular_error2.js";
+import "./circular_error3.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js
new file mode 100644
index 0000000000..5b163eab93
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error2.js
@@ -0,0 +1 @@
+import "./circular_error3.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js
new file mode 100644
index 0000000000..2589defac8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/circular_error3.js
@@ -0,0 +1 @@
+import "./circular_error1.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/empty_module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/empty_module.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/empty_module.js
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs
new file mode 100644
index 0000000000..d3f22e9ee0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/evaluation-order-setup.mjs
@@ -0,0 +1,19 @@
+globalThis.setup({allow_uncaught_exception: true});
+
+globalThis.log = [];
+
+globalThis.addEventListener("error",
+ event => globalThis.log.push("global-error", event.error.message));
+globalThis.addEventListener("onunhandledrejection",
+ event => globalThis.log.push('unhandled-promise-rejection'));
+globalThis.addEventListener("load",
+ event => globalThis.log.push("global-load"));
+
+globalThis.unreachable = function() {
+ globalThis.log.push("unreachable");
+}
+
+globalThis.test_load = async_test("Test evaluation order of modules");
+globalThis.testDone = globalThis.test_load.step_func_done(() => {
+ assert_array_equals(globalThis.log, globalThis.expectedLog);
+});
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js
new file mode 100644
index 0000000000..a2e2875f20
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/import_resolve_failure.js
@@ -0,0 +1,2 @@
+// Import from an unresolvable module specifier.
+import {x} from "unresolvable";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js
new file mode 100644
index 0000000000..282fd2ed62
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/indirect_export_resolve_failure.js
@@ -0,0 +1,2 @@
+// Export from an unresolvable module specifier.
+export {x, y} from "unresolvable";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js
new file mode 100644
index 0000000000..885db02dde
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_import.js
@@ -0,0 +1,2 @@
+// Import a non-existent export to trigger instantiation failure.
+import {not_found} from "./module.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js
new file mode 100644
index 0000000000..8494031b09
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/missing_indirect_export.js
@@ -0,0 +1,2 @@
+// Import a non-existent export to trigger instantiation failure.
+export {x, not_found} from "./module.js";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js
new file mode 100644
index 0000000000..371018f1f4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import.js
@@ -0,0 +1 @@
+export * from "http://web-platform.test:8000/_mozilla/html/semantics/scripting-1/the-script-element/support/mixed_import2.js"
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js
new file mode 100644
index 0000000000..60c6c8d8b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/mixed_import2.js
@@ -0,0 +1 @@
+export default "foo";
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js
new file mode 100644
index 0000000000..1269686475
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module.js
@@ -0,0 +1,2 @@
+export let x = 42;
+moduleRan = true;
diff --git a/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js
new file mode 100644
index 0000000000..3bd872b2e6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/semantics/scripting-1/the-script-element/support/module_eval_error.js
@@ -0,0 +1,3 @@
+// A module that throws when evaluated.
+
+this = 0;
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/README.md b/testing/web-platform/mozilla/tests/html/syntax/charset/README.md
new file mode 100644
index 0000000000..0558ae1cd5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/README.md
@@ -0,0 +1,7 @@
+The tests in this directory intentionally differ from WebKit and Blink.
+
+These are case where using the real tree builder (with `noscript`) parsing
+as in the scripting enabled mode and with CDATA sections parsing with
+awareness of foreign content differs from WebKit's and Blink's behavior
+that works as if there was a pre-foreign content, pre-template tree builder
+running in the scripting disabled mode.
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html
new file mode 100644
index 0000000000..71ef9144e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-after-template.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-noscript-after-template-ref.html">
+<noscript><template></template><meta charset="windows-1251"></noscript>
+</head>
+<body>
+<p>Meta in <code>noscript</code> after <code>template</code> (which is also inside the <code>noscript</code>).</p>
+<p>Test: </p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html
new file mode 100644
index 0000000000..645f151b26
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript-ncr.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-noscript-ncr-ref.html">
+<noscript><meta charset="&#119;indows-1251"></noscript>
+</head>
+<body>
+<p>Meta with NCR in the encoding label in <code>noscript</code>.</p>
+<p>Test: </p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html
new file mode 100644
index 0000000000..e76054d618
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-noscript.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-noscript-ref.html">
+<noscript><meta charset="windows-1251"></noscript>
+</head>
+<body>
+<p>Meta in <code>noscript</code>.</p>
+<p>Test: </p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html b/testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html
new file mode 100644
index 0000000000..56783b7afc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/in-svg-in-cdata-after-gt.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<link rel="mismatch" href="references/in-svg-in-cdata-after-gt-ref.html">
+</head>
+<body>
+<svg><![CDATA[><meta charset="windows-1251">]]></svg>
+<p>In SVG in CDATA after greater-than sign in the CDATA (after head).</p>
+<p>Test: </p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html
new file mode 100644
index 0000000000..27defe54c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-after-template-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<p>Meta in <code>noscript</code> after <code>template</code> (which is also inside the <code>noscript</code>).</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html
new file mode 100644
index 0000000000..3581ab68db
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ncr-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<p>Meta with NCR in the encoding label in <code>noscript</code>.</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html
new file mode 100644
index 0000000000..9bb9f24b88
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-noscript-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<p>Meta in <code>noscript</code>.</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html
new file mode 100644
index 0000000000..2868f47fc2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/html/syntax/charset/references/in-svg-in-cdata-after-gt-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<svg></svg>
+<p>In SVG in CDATA after greater-than sign in the CDATA (after head).</p>
+<p>Test: ж</p>
+<p>If &#x0436;, meta takes effect</p>
+</body>
diff --git a/testing/web-platform/mozilla/tests/indic-detection/LICENSE b/testing/web-platform/mozilla/tests/indic-detection/LICENSE
new file mode 100644
index 0000000000..604209a804
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/LICENSE
@@ -0,0 +1,359 @@
+Creative Commons Legal Code
+
+Attribution-ShareAlike 3.0 Unported
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
+ DAMAGES RESULTING FROM ITS USE.
+
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
+COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
+COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
+TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
+BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
+CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.
+
+1. Definitions
+
+ a. "Adaptation" means a work based upon the Work, or upon the Work and
+ other pre-existing works, such as a translation, adaptation,
+ derivative work, arrangement of music or other alterations of a
+ literary or artistic work, or phonogram or performance and includes
+ cinematographic adaptations or any other form in which the Work may be
+ recast, transformed, or adapted including in any form recognizably
+ derived from the original, except that a work that constitutes a
+ Collection will not be considered an Adaptation for the purpose of
+ this License. For the avoidance of doubt, where the Work is a musical
+ work, performance or phonogram, the synchronization of the Work in
+ timed-relation with a moving image ("synching") will be considered an
+ Adaptation for the purpose of this License.
+ b. "Collection" means a collection of literary or artistic works, such as
+ encyclopedias and anthologies, or performances, phonograms or
+ broadcasts, or other works or subject matter other than works listed
+ in Section 1(f) below, which, by reason of the selection and
+ arrangement of their contents, constitute intellectual creations, in
+ which the Work is included in its entirety in unmodified form along
+ with one or more other contributions, each constituting separate and
+ independent works in themselves, which together are assembled into a
+ collective whole. A work that constitutes a Collection will not be
+ considered an Adaptation (as defined below) for the purposes of this
+ License.
+ c. "Creative Commons Compatible License" means a license that is listed
+ at https://creativecommons.org/compatiblelicenses that has been
+ approved by Creative Commons as being essentially equivalent to this
+ License, including, at a minimum, because that license: (i) contains
+ terms that have the same purpose, meaning and effect as the License
+ Elements of this License; and, (ii) explicitly permits the relicensing
+ of adaptations of works made available under that license under this
+ License or a Creative Commons jurisdiction license with the same
+ License Elements as this License.
+ d. "Distribute" means to make available to the public the original and
+ copies of the Work or Adaptation, as appropriate, through sale or
+ other transfer of ownership.
+ e. "License Elements" means the following high-level license attributes
+ as selected by Licensor and indicated in the title of this License:
+ Attribution, ShareAlike.
+ f. "Licensor" means the individual, individuals, entity or entities that
+ offer(s) the Work under the terms of this License.
+ g. "Original Author" means, in the case of a literary or artistic work,
+ the individual, individuals, entity or entities who created the Work
+ or if no individual or entity can be identified, the publisher; and in
+ addition (i) in the case of a performance the actors, singers,
+ musicians, dancers, and other persons who act, sing, deliver, declaim,
+ play in, interpret or otherwise perform literary or artistic works or
+ expressions of folklore; (ii) in the case of a phonogram the producer
+ being the person or legal entity who first fixes the sounds of a
+ performance or other sounds; and, (iii) in the case of broadcasts, the
+ organization that transmits the broadcast.
+ h. "Work" means the literary and/or artistic work offered under the terms
+ of this License including without limitation any production in the
+ literary, scientific and artistic domain, whatever may be the mode or
+ form of its expression including digital form, such as a book,
+ pamphlet and other writing; a lecture, address, sermon or other work
+ of the same nature; a dramatic or dramatico-musical work; a
+ choreographic work or entertainment in dumb show; a musical
+ composition with or without words; a cinematographic work to which are
+ assimilated works expressed by a process analogous to cinematography;
+ a work of drawing, painting, architecture, sculpture, engraving or
+ lithography; a photographic work to which are assimilated works
+ expressed by a process analogous to photography; a work of applied
+ art; an illustration, map, plan, sketch or three-dimensional work
+ relative to geography, topography, architecture or science; a
+ performance; a broadcast; a phonogram; a compilation of data to the
+ extent it is protected as a copyrightable work; or a work performed by
+ a variety or circus performer to the extent it is not otherwise
+ considered a literary or artistic work.
+ i. "You" means an individual or entity exercising rights under this
+ License who has not previously violated the terms of this License with
+ respect to the Work, or who has received express permission from the
+ Licensor to exercise rights under this License despite a previous
+ violation.
+ j. "Publicly Perform" means to perform public recitations of the Work and
+ to communicate to the public those public recitations, by any means or
+ process, including by wire or wireless means or public digital
+ performances; to make available to the public Works in such a way that
+ members of the public may access these Works from a place and at a
+ place individually chosen by them; to perform the Work to the public
+ by any means or process and the communication to the public of the
+ performances of the Work, including by public digital performance; to
+ broadcast and rebroadcast the Work by any means including signs,
+ sounds or images.
+ k. "Reproduce" means to make copies of the Work by any means including
+ without limitation by sound or visual recordings and the right of
+ fixation and reproducing fixations of the Work, including storage of a
+ protected performance or phonogram in digital form or other electronic
+ medium.
+
+2. Fair Dealing Rights. Nothing in this License is intended to reduce,
+limit, or restrict any uses free from copyright or rights arising from
+limitations or exceptions that are provided for in connection with the
+copyright protection under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License,
+Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+perpetual (for the duration of the applicable copyright) license to
+exercise the rights in the Work as stated below:
+
+ a. to Reproduce the Work, to incorporate the Work into one or more
+ Collections, and to Reproduce the Work as incorporated in the
+ Collections;
+ b. to create and Reproduce Adaptations provided that any such Adaptation,
+ including any translation in any medium, takes reasonable steps to
+ clearly label, demarcate or otherwise identify that changes were made
+ to the original Work. For example, a translation could be marked "The
+ original work was translated from English to Spanish," or a
+ modification could indicate "The original work has been modified.";
+ c. to Distribute and Publicly Perform the Work including as incorporated
+ in Collections; and,
+ d. to Distribute and Publicly Perform Adaptations.
+ e. For the avoidance of doubt:
+
+ i. Non-waivable Compulsory License Schemes. In those jurisdictions in
+ which the right to collect royalties through any statutory or
+ compulsory licensing scheme cannot be waived, the Licensor
+ reserves the exclusive right to collect such royalties for any
+ exercise by You of the rights granted under this License;
+ ii. Waivable Compulsory License Schemes. In those jurisdictions in
+ which the right to collect royalties through any statutory or
+ compulsory licensing scheme can be waived, the Licensor waives the
+ exclusive right to collect such royalties for any exercise by You
+ of the rights granted under this License; and,
+ iii. Voluntary License Schemes. The Licensor waives the right to
+ collect royalties, whether individually or, in the event that the
+ Licensor is a member of a collecting society that administers
+ voluntary licensing schemes, via that society, from any exercise
+ by You of the rights granted under this License.
+
+The above rights may be exercised in all media and formats whether now
+known or hereafter devised. The above rights include the right to make
+such modifications as are technically necessary to exercise the rights in
+other media and formats. Subject to Section 8(f), all rights not expressly
+granted by Licensor are hereby reserved.
+
+4. Restrictions. The license granted in Section 3 above is expressly made
+subject to and limited by the following restrictions:
+
+ a. You may Distribute or Publicly Perform the Work only under the terms
+ of this License. You must include a copy of, or the Uniform Resource
+ Identifier (URI) for, this License with every copy of the Work You
+ Distribute or Publicly Perform. You may not offer or impose any terms
+ on the Work that restrict the terms of this License or the ability of
+ the recipient of the Work to exercise the rights granted to that
+ recipient under the terms of the License. You may not sublicense the
+ Work. You must keep intact all notices that refer to this License and
+ to the disclaimer of warranties with every copy of the Work You
+ Distribute or Publicly Perform. When You Distribute or Publicly
+ Perform the Work, You may not impose any effective technological
+ measures on the Work that restrict the ability of a recipient of the
+ Work from You to exercise the rights granted to that recipient under
+ the terms of the License. This Section 4(a) applies to the Work as
+ incorporated in a Collection, but this does not require the Collection
+ apart from the Work itself to be made subject to the terms of this
+ License. If You create a Collection, upon notice from any Licensor You
+ must, to the extent practicable, remove from the Collection any credit
+ as required by Section 4(c), as requested. If You create an
+ Adaptation, upon notice from any Licensor You must, to the extent
+ practicable, remove from the Adaptation any credit as required by
+ Section 4(c), as requested.
+ b. You may Distribute or Publicly Perform an Adaptation only under the
+ terms of: (i) this License; (ii) a later version of this License with
+ the same License Elements as this License; (iii) a Creative Commons
+ jurisdiction license (either this or a later license version) that
+ contains the same License Elements as this License (e.g.,
+ Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
+ License. If you license the Adaptation under one of the licenses
+ mentioned in (iv), you must comply with the terms of that license. If
+ you license the Adaptation under the terms of any of the licenses
+ mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
+ comply with the terms of the Applicable License generally and the
+ following provisions: (I) You must include a copy of, or the URI for,
+ the Applicable License with every copy of each Adaptation You
+ Distribute or Publicly Perform; (II) You may not offer or impose any
+ terms on the Adaptation that restrict the terms of the Applicable
+ License or the ability of the recipient of the Adaptation to exercise
+ the rights granted to that recipient under the terms of the Applicable
+ License; (III) You must keep intact all notices that refer to the
+ Applicable License and to the disclaimer of warranties with every copy
+ of the Work as included in the Adaptation You Distribute or Publicly
+ Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
+ You may not impose any effective technological measures on the
+ Adaptation that restrict the ability of a recipient of the Adaptation
+ from You to exercise the rights granted to that recipient under the
+ terms of the Applicable License. This Section 4(b) applies to the
+ Adaptation as incorporated in a Collection, but this does not require
+ the Collection apart from the Adaptation itself to be made subject to
+ the terms of the Applicable License.
+ c. If You Distribute, or Publicly Perform the Work or any Adaptations or
+ Collections, You must, unless a request has been made pursuant to
+ Section 4(a), keep intact all copyright notices for the Work and
+ provide, reasonable to the medium or means You are utilizing: (i) the
+ name of the Original Author (or pseudonym, if applicable) if supplied,
+ and/or if the Original Author and/or Licensor designate another party
+ or parties (e.g., a sponsor institute, publishing entity, journal) for
+ attribution ("Attribution Parties") in Licensor's copyright notice,
+ terms of service or by other reasonable means, the name of such party
+ or parties; (ii) the title of the Work if supplied; (iii) to the
+ extent reasonably practicable, the URI, if any, that Licensor
+ specifies to be associated with the Work, unless such URI does not
+ refer to the copyright notice or licensing information for the Work;
+ and (iv) , consistent with Ssection 3(b), in the case of an
+ Adaptation, a credit identifying the use of the Work in the Adaptation
+ (e.g., "French translation of the Work by Original Author," or
+ "Screenplay based on original Work by Original Author"). The credit
+ required by this Section 4(c) may be implemented in any reasonable
+ manner; provided, however, that in the case of a Adaptation or
+ Collection, at a minimum such credit will appear, if a credit for all
+ contributing authors of the Adaptation or Collection appears, then as
+ part of these credits and in a manner at least as prominent as the
+ credits for the other contributing authors. For the avoidance of
+ doubt, You may only use the credit required by this Section for the
+ purpose of attribution in the manner set out above and, by exercising
+ Your rights under this License, You may not implicitly or explicitly
+ assert or imply any connection with, sponsorship or endorsement by the
+ Original Author, Licensor and/or Attribution Parties, as appropriate,
+ of You or Your use of the Work, without the separate, express prior
+ written permission of the Original Author, Licensor and/or Attribution
+ Parties.
+ d. Except as otherwise agreed in writing by the Licensor or as may be
+ otherwise permitted by applicable law, if You Reproduce, Distribute or
+ Publicly Perform the Work either by itself or as part of any
+ Adaptations or Collections, You must not distort, mutilate, modify or
+ take other derogatory action in relation to the Work which would be
+ prejudicial to the Original Author's honor or reputation. Licensor
+ agrees that in those jurisdictions (e.g. Japan), in which any exercise
+ of the right granted in Section 3(b) of this License (the right to
+ make Adaptations) would be deemed to be a distortion, mutilation,
+ modification or other derogatory action prejudicial to the Original
+ Author's honor and reputation, the Licensor will waive or not assert,
+ as appropriate, this Section, to the fullest extent permitted by the
+ applicable national law, to enable You to reasonably exercise Your
+ right under Section 3(b) of this License (right to make Adaptations)
+ but not otherwise.
+
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
+OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
+KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
+INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
+FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
+LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
+WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
+OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
+LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
+ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
+ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+ a. This License and the rights granted hereunder will terminate
+ automatically upon any breach by You of the terms of this License.
+ Individuals or entities who have received Adaptations or Collections
+ from You under this License, however, will not have their licenses
+ terminated provided such individuals or entities remain in full
+ compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
+ survive any termination of this License.
+ b. Subject to the above terms and conditions, the license granted here is
+ perpetual (for the duration of the applicable copyright in the Work).
+ Notwithstanding the above, Licensor reserves the right to release the
+ Work under different license terms or to stop distributing the Work at
+ any time; provided, however that any such election will not serve to
+ withdraw this License (or any other license that has been, or is
+ required to be, granted under the terms of this License), and this
+ License will continue in full force and effect unless terminated as
+ stated above.
+
+8. Miscellaneous
+
+ a. Each time You Distribute or Publicly Perform the Work or a Collection,
+ the Licensor offers to the recipient a license to the Work on the same
+ terms and conditions as the license granted to You under this License.
+ b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
+ offers to the recipient a license to the original Work on the same
+ terms and conditions as the license granted to You under this License.
+ c. If any provision of this License is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of
+ the remainder of the terms of this License, and without further action
+ by the parties to this agreement, such provision shall be reformed to
+ the minimum extent necessary to make such provision valid and
+ enforceable.
+ d. No term or provision of this License shall be deemed waived and no
+ breach consented to unless such waiver or consent shall be in writing
+ and signed by the party to be charged with such waiver or consent.
+ e. This License constitutes the entire agreement between the parties with
+ respect to the Work licensed here. There are no understandings,
+ agreements or representations with respect to the Work not specified
+ here. Licensor shall not be bound by any additional provisions that
+ may appear in any communication from You. This License may not be
+ modified without the mutual written agreement of the Licensor and You.
+ f. The rights granted under, and the subject matter referenced, in this
+ License were drafted utilizing the terminology of the Berne Convention
+ for the Protection of Literary and Artistic Works (as amended on
+ September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
+ Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
+ and the Universal Copyright Convention (as revised on July 24, 1971).
+ These rights and subject matter take effect in the relevant
+ jurisdiction in which the License terms are sought to be enforced
+ according to the corresponding provisions of the implementation of
+ those treaty provisions in the applicable national law. If the
+ standard suite of rights granted under applicable copyright law
+ includes additional rights not granted under this License, such
+ additional rights are deemed to be included in the License; this
+ License is not intended to restrict the license of any rights under
+ applicable law.
+
+
+Creative Commons Notice
+
+ Creative Commons is not a party to this License, and makes no warranty
+ whatsoever in connection with the Work. Creative Commons will not be
+ liable to You or any party on any legal theory for any damages
+ whatsoever, including without limitation any general, special,
+ incidental or consequential damages arising in connection to this
+ license. Notwithstanding the foregoing two (2) sentences, if Creative
+ Commons has expressly identified itself as the Licensor hereunder, it
+ shall have all rights and obligations of Licensor.
+
+ Except for the limited purpose of indicating to the public that the
+ Work is licensed under the CCPL, Creative Commons does not authorize
+ the use by either party of the trademark "Creative Commons" or any
+ related trademark or logo of Creative Commons without the prior
+ written consent of Creative Commons. Any permitted use will be in
+ compliance with Creative Commons' then-current trademark usage
+ guidelines, as may be published on its website or otherwise made
+ available upon request from time to time. For the avoidance of doubt,
+ this trademark restriction does not form part of the License.
+
+ Creative Commons may be contacted at https://creativecommons.org/.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/README.txt b/testing/web-platform/mozilla/tests/indic-detection/README.txt
new file mode 100644
index 0000000000..9fa13a660a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/README.txt
@@ -0,0 +1,14 @@
+The text (non-markup/JavaScript) content of the files in this directory originates from Wikipedia and
+is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported license
+<https://creativecommons.org/licenses/by-sa/3.0/legalcode>.
+
+The content comes from the following articles (and their revisions):
+https://hi.wikipedia.org/w/index.php?title=%E0%A4%AE%E0%A4%82%E0%A4%97%E0%A4%B2_%E0%A4%97%E0%A5%8D%E0%A4%B0%E0%A4%B9&oldid=5105576
+https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711
+
+This directory tests that content meant for intentionally mis-encoded legacy Devanagari and Tamil fonts that Chrome's encoding detector knows about is detected as windows-1252. These fonts assign Devanagari or Tamil glyphs to code points that are symbols or Latin characters in windows-1252. In chardetng, the detection mechanism is determining that the content isn't in any chardetng-supported encoding and, therefore, the fallback is windows-1252.
+
+Tests are missing for the following fonts that Chrome knows about:
+LT TM Barani
+TMNews
+TamilWeb
diff --git a/testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html b/testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html
new file mode 100644
index 0000000000..c8de3ec2a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/baskar-jagran.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ע ע UUU UUU Q , " " UUU ע UUU UUU - "S " ٷUUU
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://hi.wikipedia.org/w/index.php?title=%E0%A4%AE%E0%A4%82%E0%A4%97%E0%A4%B2_%E0%A4%97%E0%A5%8D%E0%A4%B0%E0%A4%B9&oldid=5105576">&#2350;&#2306;&#2327;&#2354; &#2327;&#2381;&#2352;&#2361;</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/elango.html b/testing/web-platform/mozilla/tests/indic-detection/elango.html
new file mode 100644
index 0000000000..175917cfd8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/elango.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+NYY֚ (Mars) sVeh|T E[ J Lַ Bh. C sV]X죋 SLY L[L E[.Ceh|T LopV L[] Reh A|RRL CWQPY pV L[L NYY֚ Cef\. U]yz] CeLֺeh T֟eLP TVWo syz[]. CR UTWT둥 LQTT| C BeN| CeL[o NS\ULe Ly|f\.[12] CR]XV CRho NYY֚ G\ TV HTyP. J N֟ L[] CR UTWT NW] E[T֥ fQQe hZL[, E[ T\ GUXL, T[RehL, TXY]jL, T]zV YT ThL[ LQP. NYY֛ rZpeLX, TY Uּ\jL eh E[RT T\YV. sV UQPX L EVWU] JXTr UX, LTTV NjhT T[RehL J\] U] T[Reh NYY֛XV E[].
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/htchanakya.html b/testing/web-platform/mozilla/tests/indic-detection/htchanakya.html
new file mode 100644
index 0000000000..27c1ce5510
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/htchanakya.html
@@ -0,0 +1,12 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+״ U״ XW XW UQ , " " XW U״ XW U XW - "S " XW U " " XWU f XW U, ״ XW S U XW UJ U XW U XW U f XW , , US U VLW YW XW U״ XW XW W , ܳ i ״ U S XWi UU U S XW XW , ״ XW J XW U XW f XW U XW XW UXW XW
+
+v~{z UU y XW mU XW ״ XW XW XW U U S XW XW U U U XW Ϧ XW XW U U U U, VLW , ܴ U U m XW U ι , XW striations XW G XWU XW mU XW ⴿ U XW MW XW U XW λ h U XW U XW XW XWU U YWU , U ״ XW f XW , ״ XW SU XW PJ XW
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://hi.wikipedia.org/w/index.php?title=%E0%A4%AE%E0%A4%82%E0%A4%97%E0%A4%B2_%E0%A4%97%E0%A5%8D%E0%A4%B0%E0%A4%B9&oldid=5105576">&#2350;&#2306;&#2327;&#2354; &#2327;&#2381;&#2352;&#2361;</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/shreetam.html b/testing/web-platform/mozilla/tests/indic-detection/shreetam.html
new file mode 100644
index 0000000000..1fcf4ae40f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/shreetam.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+\ (Mars) `USkzv E J P BS. Cx `ۼx |Px PP Ex.CUSkzv Pa]ԯ P uUS AkzuuP Cshx ]ԯ PP \ CUQx. miں CUPUS UPh a `miں. Cu أ Pnk C BU\k CUPa \{өPU PmkQx.[12] Cu CuSa \ G Hأmhx. J \ P Cu أ \v Ex QsnU SPͲ, ° Ex GP, zuUSP, [P, ۉi x SvPͲ Pshx. \ _]UP, [PЮ US Eu . ` shzx P E J_ , P \[Szx zuUSP J ں zuUS \ E.
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tab.html b/testing/web-platform/mozilla/tests/indic-detection/tab.html
new file mode 100644
index 0000000000..1dc5ee85bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tab.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ (Mars) 袰 ݰ. ޶  .袰 袰 ܴ ޼. 좮 被袰 좮. ޼ 袬 被裬 좴.[12] . , , 袰, , ͮ . , 袰 .  , 颰 袰 袰 .
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tam.html b/testing/web-platform/mozilla/tests/indic-detection/tam.html
new file mode 100644
index 0000000000..8b4f9d0cbf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tam.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+ (Mars) K򂰴F 補 ݰ. ޶ KQL  .ނF ICP ‚ ܴ CP ޼A. ނ 𣘂O ņ. H ޼ ݂ ނ裬 G 裆A.[12]  . M꣘ FQ A N, MJ K, , , Qͮ F 裇. J C, M . K 􈶜 I L , I芪K ꃰ  K  J .
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tboomi.html b/testing/web-platform/mozilla/tests/indic-detection/tboomi.html
new file mode 100644
index 0000000000..3976416449
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tboomi.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+%NY (Mars) wVfjiT E[ J &L Bj. C wVë SLY &L[L E[.CfjiT LotV &L[] Rfj AiRRL CWPY tV &L[L %NY Cfg\. &U]}~] Cf&Lfj &TfLP %TV+Wo w}~[]. CR &UTW LQTi Cö Bf+Ni Cf&L+[o %N\ULf L}ig\.[12] CR]&X&V CRjo %NY G\ %TV HT}P. J N &L[] CR &UTW NW E[&T gQf jL+[, ˼ E[ &T\ GU+XL, T[RfjL, T+XY]lL, T~V Y TjL+[ %LP. %NY vZtfLX, TY U\lLض fj E[+R &T\+Y&V. wV UPX L EVWU] JƶTv U+X, L%TV %Nlj T[RfjL J\] U] T[Rfj %NY&X&V E[].
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/indic-detection/tscii.html b/testing/web-platform/mozilla/tests/indic-detection/tscii.html
new file mode 100644
index 0000000000..c9d47b4f9f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/indic-detection/tscii.html
@@ -0,0 +1,10 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+š (Mars) â . âɢĢ ǡ . Ȣ ǡ Ҿ 򾾡 Ȣ ǡ š 츢. ɡ 째 츼Ǣ ¨ . 쨺 째 θ.[12] ɡħ š . Ţ ǡ ɢ Ч Ƣ, Ţ¢ âĸ, ̸, , ɢ ̾ . š¢ 측, Ţ Ǩ Ȩŧ. â Ģ , â ̸ ȡ â š¢ħ .
+<script>
+test(function() {
+ assert_equals(document.characterSet, "windows-1252");
+},"Should fall back to windows-1252");
+</script>
+The text content above is a converted extract from the start of the Wikipedia article <a href="https://ta.wikipedia.org/w/index.php?title=%E0%AE%9A%E0%AF%86%E0%AE%B5%E0%AF%8D%E0%AE%B5%E0%AE%BE%E0%AE%AF%E0%AF%8D_(%E0%AE%95%E0%AF%8B%E0%AE%B3%E0%AF%8D)&oldid=3129711">&#2970;&#3014;&#2997;&#3021;&#2997;&#3006;&#2991;&#3021; (&#2965;&#3019;&#2995;&#3021;)</a> and
+is licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/legalcode">Creative Commons Attribution-ShareAlike 3.0 Unported</a> license.
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html
new file mode 100644
index 0000000000..584620392c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-0.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="match" href="prefs-false.html"/>
+<div id="result"></div>
+<script>
+// This can be any pref, as long as it doesn't affect the test and the default value is false
+// If it's updated here prefs-1.html and prefs-2.html need the same update
+result.innerHTML = SpecialPowers.getBoolPref("apz.allow_zooming_out");
+document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html
new file mode 100644
index 0000000000..8f1029d3ff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="match" href="prefs-true.html"/>
+<div id="result"></div>
+<script>
+// If the pref is changed here, it needs to also be changed in the corresponding ini file
+result.innerHTML = SpecialPowers.getBoolPref("apz.allow_zooming_out");
+document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html
new file mode 100644
index 0000000000..54ebde2ebe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<link rel="match" href="prefs-false.html"/>
+<div id="result"></div>
+<script>
+result.innerHTML = SpecialPowers.getBoolPref("apz.allow_zooming_out");
+document.documentElement.classList.remove("reftest-wait");
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html
new file mode 100644
index 0000000000..667d1e1bf6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-false.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<div id="result">false</div>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html
new file mode 100644
index 0000000000..df3fe20eff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/prefs/prefs-true.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<div id="result">true</div>
+</html>
diff --git a/testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html b/testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html
new file mode 100644
index 0000000000..38615028d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/infrastructure/specialPowers/specialpowers.html
@@ -0,0 +1,7 @@
+<title>Check specialPowers is available in gecko-only tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+test(() => assert_equals(SpecialPowers.sanityCheck(), "foo"))
+</script>
diff --git a/testing/web-platform/mozilla/tests/mathml/README.md b/testing/web-platform/mozilla/tests/mathml/README.md
new file mode 100644
index 0000000000..092dae0251
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/README.md
@@ -0,0 +1,57 @@
+# Internal MathML tests
+
+The web-platform-tests Project provides [MathML tests](https://github.com/web-platform-tests/wpt/tree/master/mathml/)
+for the [MathML Core](https://w3c.github.io/mathml-core/) specification. This
+directory contains tests for [MathML3](https://www.w3.org/TR/MathML3/) features
+implemented in Gecko or for Gecko-specific behaviors that are not described in
+any specification:
+
+- `disabled/`: Tests for MathML handling when support is disabled. This is
+ mostly used for Tor browser's "high security" mode, see
+ [bug 1173199](https://bugzilla.mozilla.org/1173199).
+
+- `fonts`: font-related tests, such as OpenType features not handled yet in
+ MathML Core or other Gecko-specific behavior.
+
+- `mathml-console-messages.html`: Tests for Gecko-specific console warning and
+ error messages triggered by MathML markup.
+
+- `mathspaces_names`: Tests for
+ [MathML3 namedspaces](https://www.w3.org/TR/MathML3/chapter2.html#type.namedspace)
+ which are removed from MathML Core. See
+ [bug 1793549](https://bugzilla.mozilla.org/1173199).
+
+- `mathvariant`: Tests for the
+ [mathvariant attribute](https://www.w3.org/TR/MathML3/chapter3.html#presm.commatt),
+ which is reduced to the case `<mi mathvariant="normal">` in MathML
+ Core. See [bug 1821980](https://bugzilla.mozilla.org/1821980).
+
+- `mpadded`: Tests for some
+ [mpadded](https://www.w3.org/TR/MathML3/chapter3.html#presm.mpadded)
+ features, which are not in the initial version of MathML Core.
+
+- `menclose`: Tests for the
+ [menclose](https://www.w3.org/TR/MathML3/chapter3.html#presm.menclose)
+ element, which is not in the initial version of MathML Core.
+ See [issue 216](https://github.com/w3c/mathml/issues/216).
+
+- `mo-accent`: Tests for the
+ [mo@accent attribute](https://www.w3.org/TR/MathML3/chapter3.html#presm.mo.dict.attrs),
+ and the corresponding accent property from the dictionary,
+ which are removed from MathML Core.
+ See [bug 1790548](https://bugzilla.mozilla.org/1790548)
+ and [bug 1636428](https://bugzilla.mozilla.org/1636428).
+
+- `negative-lengths`: Tests for negative lengths, for which support or
+ interpretation is unclear in current version of MathML Core.
+ See [issue 132](https://github.com/w3c/mathml-core/issues/132).
+
+- `tables`: Tests for
+ [table features](https://www.w3.org/TR/MathML3/chapter3.html#presm.tabmat)
+ that are in the initial version of MathML Core.
+ See [issue 125](https://github.com/w3c/mathml-core/issues/125).
+
+- `whitespace-trimming`: Tests for
+ [whitespace trimming in token elements](https://www.w3.org/TR/MathML3/chapter2.html#fund.collapse)
+ which is not described in the initial version of MathML Core.
+ See [issue 149](https://github.com/w3c/mathml-core/issues/149).
diff --git a/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.html b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.html
new file mode 100644
index 0000000000..991a828085
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>scriptlevel</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+
+ <!-- Test scriptlevel on mstyle -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <mtext>O</mtext>
+ <mstyle scriptlevel="1"><mtext>O</mtext></mstyle>
+ </mstyle>
+ </randomelement>
+
+ <!-- The mfrac element sets displaystyle to "false", or if it was already
+ false increments scriptlevel by 1, within numerator and denominator.
+ -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <mstyle displaystyle="false">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ <mstyle displaystyle="true">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ </mstyle>
+ </randomelement>
+
+ <!-- The mroot element increments scriptlevel by 2, and sets
+ displaystyle to "false", within index, but leaves both attributes
+ unchanged within base.
+ The msqrt element leaves both attributes unchanged within its
+ argument. -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <mroot>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mroot>
+ <msqrt>
+ <mtext>O</mtext>
+ </msqrt>
+ </mstyle>
+ </randomelement>
+
+<!--
+ The msub element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within subscript, but leaves both attributes unchanged within base.
+
+ The msup element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within superscript, but leaves both attributes unchanged within
+ base.
+
+ The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
+ to "false", within subscript and superscript, but leaves both attributes
+ unchanged within base.
+
+ The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
+ to "false", within each of its arguments except base, but leaves both
+ attributes unchanged within base.
+ -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <msub>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msub>
+ <msup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msup>
+ <msubsup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msubsup>
+ <mmultiscripts>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mprescripts/>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mmultiscripts>
+ </mstyle>
+ </randomelement>
+
+<!--
+ The munder element [...] always sets displaystyle to "false" within the
+ underscript, but increments scriptlevel by 1 only when accentunder is
+ "false". Within base, it always leaves both attributes unchanged.
+
+ The mover element [...] always sets displaystyle to "false" within
+ overscript, but increments scriptlevel by 1 only when accent is "false".
+ Within base, it always leaves both attributes unchanged.
+
+ The munderover [..] always sets displaystyle to "false" within underscript
+ and overscript, but increments scriptlevel by 1 only when accentunder or
+ accent, respectively, are "false". Within base, it always leaves both
+ attributes unchanged.
+-->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <munder>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munder>
+ <mover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mover>
+ <munderover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munderover>
+ </mstyle>
+ </randomelement>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.xhtml b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.xhtml
new file mode 100644
index 0000000000..5c5799ddd7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1-ref.xhtml
@@ -0,0 +1,133 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>scriptlevel</title>
+ <meta charset="utf-8"/>
+ <style>
+ h2 {
+ text-align:center;
+ }
+ </style>
+ </head>
+ <body>
+
+ <!-- Test scriptlevel on mstyle -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <mtext>O</mtext>
+ <mstyle scriptlevel="1"><mtext>O</mtext></mstyle>
+ </mstyle>
+ </randomelement>
+
+ <!-- The mfrac element sets displaystyle to "false", or if it was already
+ false increments scriptlevel by 1, within numerator and denominator.
+ -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <mstyle displaystyle="false">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ <mstyle displaystyle="true">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ </mstyle>
+ </randomelement>
+
+ <!-- The mroot element increments scriptlevel by 2, and sets
+ displaystyle to "false", within index, but leaves both attributes
+ unchanged within base.
+ The msqrt element leaves both attributes unchanged within its
+ argument. -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <mroot>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mroot>
+ <msqrt>
+ <mtext>O</mtext>
+ </msqrt>
+ </mstyle>
+ </randomelement>
+
+<!--
+ The msub element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within subscript, but leaves both attributes unchanged within base.
+
+ The msup element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within superscript, but leaves both attributes unchanged within
+ base.
+
+ The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
+ to "false", within subscript and superscript, but leaves both attributes
+ unchanged within base.
+
+ The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
+ to "false", within each of its arguments except base, but leaves both
+ attributes unchanged within base.
+ -->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <msub>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msub>
+ <msup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msup>
+ <msubsup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msubsup>
+ <mmultiscripts>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mprescripts/>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mmultiscripts>
+ </mstyle>
+ </randomelement>
+
+<!--
+ The munder element [...] always sets displaystyle to "false" within the
+ underscript, but increments scriptlevel by 1 only when accentunder is
+ "false". Within base, it always leaves both attributes unchanged.
+
+ The mover element [...] always sets displaystyle to "false" within
+ overscript, but increments scriptlevel by 1 only when accent is "false".
+ Within base, it always leaves both attributes unchanged.
+
+ The munderover [..] always sets displaystyle to "false" within underscript
+ and overscript, but increments scriptlevel by 1 only when accentunder or
+ accent, respectively, are "false". Within base, it always leaves both
+ attributes unchanged.
+-->
+ <randomelement>
+ <mstyle scriptsizemultiplier="2">
+ <munder>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munder>
+ <mover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mover>
+ <munderover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munderover>
+ </mstyle>
+ </randomelement>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.html b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.html
new file mode 100644
index 0000000000..ea5ff9272e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>scriptlevel</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="disabled-scriptlevel-1-ref.html"/>
+ </head>
+ <body>
+
+ <!-- Test scriptlevel on mstyle -->
+ <math>
+ <mstyle scriptsizemultiplier="2">
+ <mtext>O</mtext>
+ <mstyle scriptlevel="1"><mtext>O</mtext></mstyle>
+ </mstyle>
+ </math>
+
+ <!-- The mfrac element sets displaystyle to "false", or if it was already
+ false increments scriptlevel by 1, within numerator and denominator.
+ -->
+ <math>
+ <mstyle scriptsizemultiplier="2">
+ <mstyle displaystyle="false">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ <mstyle displaystyle="true">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ </mstyle>
+ </math>
+
+ <!-- The mroot element increments scriptlevel by 2, and sets
+ displaystyle to "false", within index, but leaves both attributes
+ unchanged within base.
+ The msqrt element leaves both attributes unchanged within its
+ argument. -->
+ <math>
+ <mstyle scriptsizemultiplier="2">
+ <mroot>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mroot>
+ <msqrt>
+ <mtext>O</mtext>
+ </msqrt>
+ </mstyle>
+ </math>
+
+<!--
+ The msub element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within subscript, but leaves both attributes unchanged within base.
+
+ The msup element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within superscript, but leaves both attributes unchanged within
+ base.
+
+ The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
+ to "false", within subscript and superscript, but leaves both attributes
+ unchanged within base.
+
+ The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
+ to "false", within each of its arguments except base, but leaves both
+ attributes unchanged within base.
+ -->
+ <math>
+ <mstyle scriptsizemultiplier="2">
+ <msub>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msub>
+ <msup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msup>
+ <msubsup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msubsup>
+ <mmultiscripts>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mprescripts/>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+<!--
+ The munder element [...] always sets displaystyle to "false" within the
+ underscript, but increments scriptlevel by 1 only when accentunder is
+ "false". Within base, it always leaves both attributes unchanged.
+
+ The mover element [...] always sets displaystyle to "false" within
+ overscript, but increments scriptlevel by 1 only when accent is "false".
+ Within base, it always leaves both attributes unchanged.
+
+ The munderover [..] always sets displaystyle to "false" within underscript
+ and overscript, but increments scriptlevel by 1 only when accentunder or
+ accent, respectively, are "false". Within base, it always leaves both
+ attributes unchanged.
+-->
+ <math>
+ <mstyle scriptsizemultiplier="2">
+ <munder>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munder>
+ <mover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mover>
+ <munderover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munderover>
+ </mstyle>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.xhtml b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.xhtml
new file mode 100644
index 0000000000..d571df76f0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/disabled/disabled-scriptlevel-1.xhtml
@@ -0,0 +1,134 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>scriptlevel</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="disabled-scriptlevel-1-ref.xhtml"/>
+ <style>
+ h2 {
+ text-align:center;
+ }
+ </style>
+ </head>
+ <body>
+
+ <!-- Test scriptlevel on mstyle -->
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="2">
+ <mtext>O</mtext>
+ <mstyle scriptlevel="1"><mtext>O</mtext></mstyle>
+ </mstyle>
+ </math>
+
+ <!-- The mfrac element sets displaystyle to "false", or if it was already
+ false increments scriptlevel by 1, within numerator and denominator.
+ -->
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="2">
+ <mstyle displaystyle="false">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ <mstyle displaystyle="true">
+ <mfrac>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mfrac>
+ </mstyle>
+ </mstyle>
+ </math>
+
+ <!-- The mroot element increments scriptlevel by 2, and sets
+ displaystyle to "false", within index, but leaves both attributes
+ unchanged within base.
+ The msqrt element leaves both attributes unchanged within its
+ argument. -->
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="2">
+ <mroot>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mroot>
+ <msqrt>
+ <mtext>O</mtext>
+ </msqrt>
+ </mstyle>
+ </math>
+
+<!--
+ The msub element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within subscript, but leaves both attributes unchanged within base.
+
+ The msup element [...] increments scriptlevel by 1, and sets displaystyle to
+ "false", within superscript, but leaves both attributes unchanged within
+ base.
+
+ The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
+ to "false", within subscript and superscript, but leaves both attributes
+ unchanged within base.
+
+ The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
+ to "false", within each of its arguments except base, but leaves both
+ attributes unchanged within base.
+ -->
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="2">
+ <msub>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msub>
+ <msup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msup>
+ <msubsup>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </msubsup>
+ <mmultiscripts>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mprescripts/>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+<!--
+ The munder element [...] always sets displaystyle to "false" within the
+ underscript, but increments scriptlevel by 1 only when accentunder is
+ "false". Within base, it always leaves both attributes unchanged.
+
+ The mover element [...] always sets displaystyle to "false" within
+ overscript, but increments scriptlevel by 1 only when accent is "false".
+ Within base, it always leaves both attributes unchanged.
+
+ The munderover [..] always sets displaystyle to "false" within underscript
+ and overscript, but increments scriptlevel by 1 only when accentunder or
+ accent, respectively, are "false". Within base, it always leaves both
+ attributes unchanged.
+-->
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mstyle scriptsizemultiplier="2">
+ <munder>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munder>
+ <mover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </mover>
+ <munderover>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ <mtext>O</mtext>
+ </munderover>
+ </mstyle>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/disabled/dynamic-math-tree-001.html b/testing/web-platform/mozilla/tests/mathml/disabled/dynamic-math-tree-001.html
new file mode 100644
index 0000000000..3f184e823c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/disabled/dynamic-math-tree-001.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1173199
+-->
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<script type="application/javascript">
+ const mathmlNS = "http://www.w3.org/1998/Math/MathML";
+ let t = document.getElementById('testnodes');
+
+ test(function() {
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS(mathmlNS, "math:math"));
+ assert_equals(t.firstChild.namespaceURI, mathmlNS);
+ t.firstChild.textContent = "<foo>";
+ assert_equals(t.innerHTML, "<math:math>&lt;foo&gt;</math:math>");
+ }, "Writing '<foo>' element in a dynamically created MathML element.");
+
+ test(function() {
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS(mathmlNS, "math"));
+ assert_equals(t.firstChild.namespaceURI, mathmlNS);
+ t.firstChild.appendChild(document.createElementNS(mathmlNS, "script"));
+ assert_equals(t.firstChild.firstChild.namespaceURI, mathmlNS);
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ assert_equals(t.innerHTML,
+ '<math><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></math>');
+ }, "Writing '<script>' element in a dynamically created MathML element.");
+
+ test(function() {
+ t.innerHTML = null;
+ t.appendChild(document.createElementNS(mathmlNS, "math"));
+ assert_equals(t.firstChild.namespaceURI, mathmlNS);
+ t.firstChild.appendChild(document.createElementNS(mathmlNS, "style"));
+ assert_equals(t.firstChild.firstChild.namespaceURI, mathmlNS);
+ t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+ assert_equals(t.innerHTML,
+ '<math><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></math>');
+ }, "Writing '<style>' element in a dynamically created MathML element.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/disabled/math-parse01.html b/testing/web-platform/mozilla/tests/mathml/disabled/math-parse01.html
new file mode 100644
index 0000000000..3aff716d9f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/disabled/math-parse01.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>math in html: parsing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>math in html: parsing</h1>
+
+<div id="log" style="display:block"></div>
+
+<div style="display:none">
+<div><math id="m1"><mtext/></math></div>
+<div id="d1"><math><mrow/><mi/></math></div>
+<div id="d2"><math><mrow><mrow><mn>1</mn></mrow><mi>a</mi></mrow></math></div>
+<div id="d3">&lang;&rang;</div>
+<div id="d4">&Kopf;</div>
+<div id="d5"><math><semantics><mi>a</mi><annotation-xml><foo/><bar/></annotation-xml></semantics></math></div>
+<div id="d6"><math><semantics><mi>a</mi><annotation-xml encoding="text/html"><div></div></annotation-xml></semantics><mn/></math>
+</div>
+
+
+<script>
+
+test(function() {
+assert_equals(document.getElementById("m1"),document.getElementsByTagName("math")[0]);
+},"The id attribute should be recognised on math elements");
+
+test(function() {
+assert_equals(document.getElementById("d1").firstChild.nodeName,"math")
+},"The node name should be math");
+
+test(function() {
+assert_equals(document.getElementById("d1").firstChild.namespaceURI ,"http://www.w3.org/1998/Math/MathML")
+},"math should be in MathML Namespace");
+
+test(function() {
+assert_equals(document.getElementById("d1").firstChild.childNodes.length ,2)
+},"Math has 2 children (empty tag syntax)");
+
+test(function() {
+assert_equals(document.getElementById("d2").firstChild.childNodes.length ,1)
+},"Nested mrow elements should be parsed correctly");
+
+test(function() {
+assert_equals(document.getElementById("d3").firstChild.nodeValue ,"\u27E8\u27E9")
+},"Testing rang and lang entity code points");
+
+test(function() {
+assert_equals(document.getElementById("d4").firstChild.nodeValue ,"\uD835\uDD42")
+},"Testing Kopf (Plane 1) entity code point");
+
+test(function() {
+assert_equals(document.getElementById("d5").firstChild.firstChild.childNodes[1].childNodes.length ,2)
+},"Empty element tags in annotation-xml parsed as per XML.");
+
+test(function() {
+assert_equals(document.getElementById("d6").firstChild.childNodes.length ,2)
+},"html tags allowed in annotation-xml/@encoding='text/html'.");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/README b/testing/web-platform/mozilla/tests/mathml/fonts/README
new file mode 100644
index 0000000000..ad19f66228
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/README
@@ -0,0 +1,6 @@
+The fonts in this directory are autogenerated with FontForge using the Python
+script generate.py. See the comments in that file for more information on how
+to run the script.
+
+These fonts are intended to test the The MATH table and OpenType Features used
+in MathML. See layout/reftests/mathml/ and layout/mathml/tests.
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/axis-height-1.otf b/testing/web-platform/mozilla/tests/mathml/fonts/axis-height-1.otf
new file mode 100644
index 0000000000..2d6cc2fa0a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/axis-height-1.otf
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/axis-height-2.otf b/testing/web-platform/mozilla/tests/mathml/fonts/axis-height-2.otf
new file mode 100644
index 0000000000..fdb7c97ce0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/axis-height-2.otf
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/default-font-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/default-font-ref.html
new file mode 100644
index 0000000000..9917f14b6f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/default-font-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>default font</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body style="font: 20px monospace;">
+
+ <math style="font-family: serif; font-size: 40px;">
+ <mn>x-math language</mn>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/default-font.html b/testing/web-platform/mozilla/tests/mathml/fonts/default-font.html
new file mode 100644
index 0000000000..6fead9cbce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/default-font.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>default font</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="default-font-ref.html"/>
+ </head>
+ <body style="font: 20px monospace;">
+
+ <!-- font.minimum-size.x-math set to 40 -->
+ <math>
+ <mn>x-math language</mn>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1-ref.html
new file mode 100644
index 0000000000..61f5e9c2f6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1-ref.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<head>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ math {
+ font-family: dtls-1;
+ }
+ </style>
+</head>
+<body>
+ <math>
+ <mover accent="true">
+ <mn>b</mn>
+ <mn>c</mn>
+ </mover>
+ <munder accentunder="true">
+ <mn>c</mn>
+ <mn>c</mn>
+ </munder>
+ <munderover accent="true" accentunder="true">
+ <mn>b</mn>
+ <mn>c</mn>
+ <mn>c</mn>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mover accent="true">
+ <mover accent="true">
+ <mn>b</mn>
+ <mn>b</mn>
+ </mover>
+ <mn>c</mn>
+ </mover>
+ <mover accent="true">
+ <mover accent="true">
+ <mn>b</mn>
+ <mn>b</mn>
+ </mover>
+ <mover accent="true">
+ <mn>b</mn>
+ <mn>c</mn>
+ </mover>
+ </mover>
+ <munderover accent="true" accentunder="true">
+ <munderover accent="true" accentunder="true">
+ <mn>b</mn>
+ <mn>b</mn>
+ <mn>b</mn>
+ </munderover>
+ <mn>c</mn>
+ <mn>c</mn>
+ </munderover>
+ <munderover accent="true" accentunder="true">
+ <munderover accent="true" accentunder="true">
+ <mn>b</mn>
+ <mn>b</mn>
+ <mn>b</mn>
+ </munderover>
+ <munderover accent="true" accentunder="true">
+ <mn>b</mn>
+ <mn>c</mn>
+ <mn>c</mn>
+ </munderover>
+ <munderover accent="true" accentunder="true">
+ <mn>b</mn>
+ <mn>c</mn>
+ <mn>c</mn>
+ </munderover>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mover accent="true">
+ <mn>c</mn>
+ <mn>c</mn>
+ </mover>
+ <munder accentunder="true">
+ <mn>c</mn>
+ <mn>c</mn>
+ </munder>
+ <munderover accent="true" accentunder="true">
+ <mn>c</mn>
+ <mn>c</mn>
+ <mn>c</mn>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mover accent="false">
+ <mn>c</mn>
+ <mn>c</mn>
+ </mover>
+ <munderover accent="false" accentunder="false">
+ <mn>c</mn>
+ <mn>c</mn>
+ <mn>c</mn>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mover>
+ <mo movablelimits="true">c</mo>
+ <mo>c</mo>
+ </mover>
+ <munderover>
+ <mo movablelimits="true">c</mo>
+ <mo>c</mo>
+ <mo>c</mo>
+ </munderover>
+ <mover accent="true">
+ <mo movablelimits="true">c</mo>
+ <mo>c</mo>
+ </mover>
+ <munderover accent="true">
+ <mo movablelimits="true">c</mo>
+ <mo>c</mo>
+ <mo>c</mo>
+ </munderover>
+ </math>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.html b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.html
new file mode 100644
index 0000000000..4e76a658e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<head>
+ <link rel="match" href="dtls-1-ref.html"/>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ math {
+ font-family: dtls-1;
+ }
+ </style>
+</head>
+<body>
+ <math>
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <munder accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </munder>
+ <munderover accent="true" accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mover accent="true">
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <mn>a</mn>
+ </mover>
+ <mover accent="true">
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ </mover>
+ <munderover accent="true" accentunder="true">
+ <munderover accent="true" accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ <munderover accent="true" accentunder="true">
+ <munderover accent="true" accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ <munderover accent="true" accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ <munderover accent="true" accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-feature-settings: 'dtls' 0">
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <munder accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </munder>
+ <munderover accent="true" accentunder="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mover accent="false">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <munderover accent="false" accentunder="false">
+ <mn>a</mn>
+ <mn>a</mn>
+ <mn>a</mn>
+ </munderover>
+ </math>
+
+ <p>
+
+ <math>
+ <mover>
+ <mo movablelimits="true">a</mo>
+ <mo>a</mo>
+ </mover>
+ <munderover>
+ <mo movablelimits="true">a</mo>
+ <mo>a</mo>
+ <mo>a</mo>
+ </munderover>
+ <mover accent="true">
+ <mo movablelimits="true">a</mo>
+ <mo>a</mo>
+ </mover>
+ <munderover accent="true">
+ <mo movablelimits="true">a</mo>
+ <mo>a</mo>
+ <mo>a</mo>
+ </munderover>
+ </math>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.otf b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.otf
new file mode 100644
index 0000000000..1ffbe24427
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-1.otf
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-2-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-2-ref.html
new file mode 100644
index 0000000000..49342c1aa7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-2-ref.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ </style>
+ </head>
+ <body>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>a</mo>
+ <!-- deliberately invalid -->
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mo>c</mo>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mrow>
+ <mrow>
+ <mrow>
+ <mo>b</mo>
+ </mrow>
+ </mrow>
+ </mrow>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style ="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ <mover accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle5" style ="font-family: 'dtls-1'; font-feature-settings: 'dtls' 0">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>c</mo>
+ <mo>c</mo>
+ </mover>
+ <mover accent="true">
+ <mo>c</mo>
+ <mo>c</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover id="mover1" accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="false" id="mover2">
+ <mo>c</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover id="mover3">
+ <mo>c</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo movablelimits="true">c</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-2.html b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-2.html
new file mode 100644
index 0000000000..63a42d4a22
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-2.html
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel="match" href="dtls-2-ref.html"/>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ </style>
+ </head>
+ <body>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true" id="mover0">
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>a</mo>
+ <mo id="mo0"></mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>a</mo>
+ <mo id="mo1">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';" id="mstyle0">
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle1">
+ <mover accent="true">
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ <mover accent="true">
+ <mn>a</mn>
+ <mn>a</mn>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mrow>
+ <mrow>
+ <mrow id="mrow0">
+ </mrow>
+ </mrow>
+ </mrow>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle4" style ="font-family: 'dtls-1'; font-feature-settings: 'dtls' 0">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ <mover accent="true">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle5" style ="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ <mover accent="true">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover id="mover1">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true" id="mover2">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true" id="mover3">
+ <mo>a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo id="mo2">a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo id="mo3" movablelimits="true">a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <script>
+ function doTest()
+ {
+ var mo = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+ mo.innerHTML = "a";
+ // An added child gets dtls font feature
+ document.getElementById("mover0").appendChild(mo);
+ // A child with changed text gets dtls font feature
+ document.getElementById("mo0").innerHTML = "a";
+ // A relocated child loses dtls font feature setting
+ document.getElementById("mstyle0").appendChild(document.getElementById("mo1"));
+ // A change in style
+ document.getElementById("mstyle1").setAttribute("style", "font-family: 'dtls-1';");
+
+ // dtls gets added to descendants as well
+ var mo1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+ mo1.innerHTML = "a";
+ document.getElementById("mrow0").appendChild(mo1);
+ // removing explicit dtls setting works
+ document.getElementById("mstyle4").setAttribute(
+ "style", "font-family: 'dtls-1';")
+ // setting an explicit dtls font feature
+ document.getElementById("mstyle5").setAttribute(
+ "style" , "font-family: 'dtls-1'; font-feature-settings: 'dtls' 0");
+
+ // Adding accent="true" applies dtls font feature
+ document.getElementById("mover1").setAttribute("accent", "true");
+ // Changing accent="true" to false removes dtls font feature
+ document.getElementById("mover2").setAttribute("accent", "false");
+ // Removing accent="true" removes dtls font feature
+ document.getElementById("mover3").removeAttribute("accent");
+
+ // Movablelimits disables dtls font feature
+ document.getElementById("mo2").setAttribute("movablelimits", "true");
+ // Removing movablelimits restores dtls font feature
+ document.getElementById("mo3").removeAttribute("movablelimits");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-3-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-3-ref.html
new file mode 100644
index 0000000000..ce5e5c6122
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-3-ref.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ </style>
+ </head>
+ <body>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <!-- deliberately invalid -->
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mo>&#x1d520;</mo>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d51f;</mo>
+ </mover>
+ <mover accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mrow>
+ <mrow>
+ <mrow>
+ <mo>&#x1d51f;</mo>
+ </mrow>
+ </mrow>
+ </mrow>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style ="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ <mover accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle5" style ="font-family: 'dtls-1'; font-feature-settings: 'dtls' 0">
+ <mover accent="true">
+ <mover accent="true">
+ <mo>&#x1d520;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ <mover accent="true">
+ <mo>&#x1d520;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover id="mover1" accent="true">
+ <mo>&#x1d51f;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="false" id="mover2">
+ <mo>&#x1d520;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover id="mover3">
+ <mo>&#x1d520;</mo>
+ <mo>&#x1d520;</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>&#x1d51e;</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo>b</mo>
+ <mo>c</mo>
+ </mover>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/dtls-3.html b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-3.html
new file mode 100644
index 0000000000..50eb56b28f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/dtls-3.html
@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <link rel="match" href="dtls-3-ref.html"/>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ </style>
+ </head>
+ <body>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true" id="mover0">
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur" id="mo0"></mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur" id="mo1">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';" id="mstyle0">
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle1">
+ <mover accent="true">
+ <mover accent="true">
+ <mn mathvariant="fraktur">a</mn>
+ <mn mathvariant="fraktur">a</mn>
+ </mover>
+ <mover accent="true">
+ <mn mathvariant="fraktur">a</mn>
+ <mn mathvariant="fraktur">a</mn>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mrow>
+ <mrow>
+ <mrow id="mrow0">
+ </mrow>
+ </mrow>
+ </mrow>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle4" style ="font-family: 'dtls-1'; font-feature-settings: 'dtls' 0">
+ <mover accent="true">
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle id="mstyle5" style ="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ <mover accent="true">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover id="mover1">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true" id="mover2">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true" id="mover3">
+ <mo mathvariant="fraktur">a</mo>
+ <mo mathvariant="fraktur">a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo id="mo2">a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'dtls-1';">
+ <mover accent="true">
+ <mo mathvariant="fraktur" id="mo3">a</mo>
+ <mo>a</mo>
+ </mover>
+ </mstyle>
+ </math>
+
+ <script>
+ function doTest()
+ {
+ var mo = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+ mo.innerHTML = "a";
+ mo.setAttribute("mathvariant", "fraktur");
+ // An added child gets dtls font feature
+ document.getElementById("mover0").appendChild(mo);
+ // A child with changed text gets dtls font feature
+ document.getElementById("mo0").innerHTML = "a";
+ // A relocated child loses dtls font feature setting
+ document.getElementById("mstyle0").appendChild(document.getElementById("mo1"));
+ // A change in style
+ document.getElementById("mstyle1").setAttribute("style", "font-family: 'dtls-1';");
+
+ // dtls gets added to descendants as well
+ var mo1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mo");
+ mo1.innerHTML = "a";
+ mo1.setAttribute("mathvariant", "fraktur");
+ document.getElementById("mrow0").appendChild(mo1);
+ // removing explicit dtls setting works
+ document.getElementById("mstyle4").setAttribute(
+ "style", "font-family: 'dtls-1';")
+ // setting an explicit dtls font feature
+ document.getElementById("mstyle5").setAttribute(
+ "style" , "font-family: 'dtls-1'; font-feature-settings: 'dtls' 0");
+
+ // Adding accent="true" applies dtls font feature
+ document.getElementById("mover1").setAttribute("accent", "true");
+ // Changing accent="true" to false removes dtls font feature
+ document.getElementById("mover2").setAttribute("accent", "false");
+ // Removing accent="true" removes dtls font feature
+ document.getElementById("mover3").removeAttribute("accent");
+
+ // dtls applies when mathvariant is set
+ document.getElementById("mo2").setAttribute("mathvariant", "fraktur");
+
+ // dtls still applies when mathvariant is removed
+ document.getElementById("mo3").removeAttribute("mathvariant");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1-ref.html
new file mode 100644
index 0000000000..a7716454ec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1-ref.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Font Inflation</title>
+ <meta charset="utf-8"/>
+ <style>
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ p, math {
+ font-family: serif;
+ }
+ </style>
+ </head>
+ <body>
+
+<p>The text
+'<math>
+ <mrow id="ref">
+ <mtext style="font-family: dtls-1">&#x1D51E;</mtext>
+ <mtext>+</mtext>
+ </mrow>
+ </math>'
+should have the same size as inline math
+'<math>
+ <mrow id="inline">
+ <mtext style="font-family: dtls-1">&#x1D51E;<!-- MATHEMATICAL FRAKTUR SMALL A--></mtext>
+ <mo>+</mo>
+ </mrow>
+ </math>'
+or math in a table: '<math><mtable><mtr>
+ <mtd>
+ <mrow id="cell">
+ <mtext style="font-family: dtls-1">&#x1D51E;<!-- MATHEMATICAL FRAKTUR SMALL A--></mtext>
+ <mo>+</mo>
+ </mrow>
+ </mtd>
+ </mtr></mtable></math>'
+(but not necessarily the same size as block-level math
+<math display="block">
+ <mrow id="display">
+ <mtext style="font-family: dtls-1">&#x1D51E;<!-- MATHEMATICAL FRAKTUR SMALL A--></mtext>
+ <mo>+</mo>
+ </mrow>
+ </math>
+because it forms a BFC and hence is its own inflation container.)
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem
+ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
+eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
+in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor
+sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut
+labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
+exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
+aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit
+amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
+et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
+in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
+officia deserunt mollit anim id est laborum.</p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1.html b/testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1.html
new file mode 100644
index 0000000000..3af93b2fc1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/font-inflation-1.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Font Inflation</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="font-inflation-1-ref.html"/>
+ <style>
+ @font-face {
+ font-family: "dtls-1";
+ src: url(dtls-1.otf);
+ }
+ p, math {
+ font-family: serif;
+ }
+ </style>
+ <script type="text/javascript">
+ function almostEqual(aX, aY) {
+ var epsilon = 2.6;
+ return Math.abs(aX - aY) < epsilon;
+ }
+
+ function verifySize(aElement, aReference) {
+ /* Verify if the size of the element matches the reference, and
+ otherwise paint the element in red. */
+ if (!almostEqual(aElement.getBoundingClientRect().height,
+ aReference.getBoundingClientRect().height) ||
+ !almostEqual(aElement.getBoundingClientRect().width,
+ aReference.getBoundingClientRect().width)) {
+ aElement.setAttribute("mathcolor", "red");
+ }
+ }
+
+ function verifySizes() {
+ /* Compare the size of the elements in the inline and display equations
+ against the reference mtext elements. */
+ var ref = document.getElementById("ref");
+ var inline = document.getElementById("inline");
+ var cell = document.getElementById("cell");
+ for (var i = 0; i < ref.children.length; i++) {
+ verifySize(inline.children[i], ref.children[i]);
+ verifySize(cell.children[i], ref.children[i]);
+ }
+
+ document.documentElement.removeAttribute("class");
+ }
+
+ document.documentElement.addEventListener("TestRendered", verifySizes);
+ </script>
+ </head>
+ <body>
+
+<p>The text
+'<math>
+ <mrow id="ref">
+ <mtext style="font-family: dtls-1">&#x1D51E;</mtext>
+ <mtext>+</mtext>
+ </mrow>
+ </math>'
+should have the same size as inline math
+'<math>
+ <mrow id="inline">
+ <mtext style="font-family: dtls-1">&#x1D51E;<!-- MATHEMATICAL FRAKTUR SMALL A--></mtext>
+ <mo>+</mo>
+ </mrow>
+ </math>'
+or math in a table: '<math><mtable><mtr>
+ <mtd>
+ <mrow id="cell">
+ <mtext style="font-family: dtls-1">&#x1D51E;<!-- MATHEMATICAL FRAKTUR SMALL A--></mtext>
+ <mo>+</mo>
+ </mrow>
+ </mtd>
+ </mtr></mtable></math>'
+(but not necessarily the same size as block-level math
+<math display="block">
+ <mrow id="display">
+ <mtext style="font-family: dtls-1">&#x1D51E;<!-- MATHEMATICAL FRAKTUR SMALL A--></mtext>
+ <mo>+</mo>
+ </mrow>
+ </math>
+because it forms a BFC and hence is its own inflation container.)
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem
+ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
+eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
+in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor
+sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut
+labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
+exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis
+aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit
+amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
+et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
+in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
+pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
+officia deserunt mollit anim id est laborum.</p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/generate.py b/testing/web-platform/mozilla/tests/mathml/fonts/generate.py
new file mode 100644
index 0000000000..6f5008c898
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/generate.py
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+# vim: set shiftwidth=4 tabstop=8 autoindent expandtab:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# For general fontforge documentation, see:
+# http://fontforge.sourceforge.net/
+# For fontforge scripting documentation, see:
+# http://fontforge.sourceforge.net/scripting-tutorial.html
+# http://fontforge.sourceforge.net/scripting.html
+# and most importantly:
+# http://fontforge.sourceforge.net/python.html
+
+# To install what you need, on Ubuntu,
+# sudo apt-get install python-fontforge
+
+import fontforge
+
+em = 1000
+
+
+def newMathFont(aName):
+ print("Generating %s.otf..." % aName, end="")
+ mathFont = fontforge.font()
+ mathFont.fontname = aName
+ mathFont.familyname = aName
+ mathFont.fullname = aName
+ mathFont.copyright = "Copyright (c) 2014 Mozilla Corporation"
+ mathFont.encoding = "UnicodeFull"
+
+ # Create a space character. Also force the creation of some MATH subtables
+ # so that OTS will not reject the MATH table.
+ g = mathFont.createChar(ord(" "), "space")
+ g.width = em
+ g.italicCorrection = 0
+ g.topaccent = 0
+ g.mathKern.bottomLeft = tuple([(0, 0)])
+ g.mathKern.bottomRight = tuple([(0, 0)])
+ g.mathKern.topLeft = tuple([(0, 0)])
+ g.mathKern.topRight = tuple([(0, 0)])
+ mathFont[ord(" ")].horizontalVariants = "space"
+ mathFont[ord(" ")].verticalVariants = "space"
+ return mathFont
+
+
+def saveMathFont(aFont):
+ aFont.em = em
+ aFont.ascent = aFont.descent = em / 2
+ aFont.hhea_ascent = aFont.os2_typoascent = aFont.os2_winascent = em / 2
+ aFont.descent = aFont.hhea_descent = em / 2
+ aFont.os2_typodescent = aFont.os2_windescent = em / 2
+ aFont.hhea_ascent_add = aFont.hhea_descent_add = 0
+ aFont.os2_typoascent_add = aFont.os2_typodescent_add = 0
+ aFont.os2_winascent_add = aFont.os2_windescent_add = 0
+ aFont.os2_use_typo_metrics = True
+ aFont.generate(aFont.fontname + ".otf")
+ print(" done.")
+
+
+def createSquareGlyph(aFont, aCodePoint):
+ g = aFont.createChar(aCodePoint)
+ p = g.glyphPen()
+ p.moveTo(0, 0)
+ p.lineTo(em, 0)
+ p.lineTo(em, em)
+ p.lineTo(0, em)
+ p.closePath()
+
+
+def createLLTriangleGlyph(aFont, aCodePoint):
+ g = aFont.createChar(aCodePoint)
+ p = g.glyphPen()
+ p.moveTo(0, 0)
+ p.lineTo(em, 0)
+ p.lineTo(0, em)
+ p.closePath()
+
+
+def createURTriangleGlyph(aFont, aCodePoint):
+ g = aFont.createChar(aCodePoint)
+ p = g.glyphPen()
+ p.moveTo(em, 0)
+ p.lineTo(em, em)
+ p.lineTo(0, em)
+ p.closePath()
+
+
+def createDiamondGlyph(aFont, aCodePoint):
+ g = aFont.createChar(aCodePoint)
+ p = g.glyphPen()
+ p.moveTo(0, em / 2)
+ p.lineTo(em / 2, 0)
+ p.lineTo(em, em / 2)
+ p.lineTo(em / 2, em)
+ p.closePath()
+
+
+################################################################################
+# Glyph variants and constructions
+f = newMathFont("stretchy")
+nvariants = 3
+
+# Draw boxes for the size variants and glues
+for i in range(0, nvariants):
+ s = em * (i + 1)
+
+ g = f.createChar(-1, "h%d" % i)
+ p = g.glyphPen()
+ p.moveTo(0, -em)
+ p.lineTo(0, em)
+ p.lineTo(s, em)
+ p.lineTo(s, -em)
+ p.closePath()
+ g.width = s
+
+ g = f.createChar(-1, "v%d" % i)
+ p = g.glyphPen()
+ p.moveTo(0, 0)
+ p.lineTo(0, s)
+ p.lineTo(2 * em, s)
+ p.lineTo(2 * em, 0)
+ p.closePath()
+ g.width = 2 * em
+
+# Draw some pieces for stretchy operators
+s = em * nvariants
+
+g = f.createChar(-1, "left")
+p = g.glyphPen()
+p.moveTo(0, -2 * em)
+p.lineTo(0, 2 * em)
+p.lineTo(s, em)
+p.lineTo(s, -em)
+p.closePath()
+g.width = s
+
+g = f.createChar(-1, "right")
+p = g.glyphPen()
+p.moveTo(0, -em)
+p.lineTo(0, em)
+p.lineTo(s, 2 * em)
+p.lineTo(s, -2 * em)
+p.closePath()
+g.width = s
+
+g = f.createChar(-1, "hmid")
+p = g.glyphPen()
+p.moveTo(0, -em)
+p.lineTo(0, em)
+p.lineTo(s, 2 * em)
+p.lineTo(2 * s, em)
+p.lineTo(2 * s, -em)
+p.lineTo(s, -2 * em)
+p.closePath()
+g.width = 2 * s
+
+g = f.createChar(-1, "bottom")
+p = g.glyphPen()
+p.moveTo(0, 0)
+p.lineTo(0, s)
+p.lineTo(2 * em, s)
+p.lineTo(4 * em, 0)
+p.closePath()
+g.width = 4 * em
+
+g = f.createChar(-1, "top")
+p = g.glyphPen()
+p.moveTo(0, 0)
+p.lineTo(4 * em, 0)
+p.lineTo(2 * em, -s)
+p.lineTo(0, -s)
+p.closePath()
+g.width = 4 * em
+
+g = f.createChar(-1, "vmid")
+p = g.glyphPen()
+p.moveTo(0, s)
+p.lineTo(2 * em, s)
+p.lineTo(4 * em, 0)
+p.lineTo(2 * em, -s)
+p.lineTo(0, -s)
+p.closePath()
+g.width = 3 * em
+
+# Create small rectangle of various size for some exotic arrows that are
+# unlikely to be stretchable with standard fonts.
+hstretchy = [
+ 0x219C, # leftwards wave arrow
+ 0x219D, # rightwards wave arrow
+ 0x219E, # leftwards two headed arrow
+ 0x21A0, # rightwards two headed arrow
+ 0x21A2, # leftwards arrow with tail
+]
+vstretchy = [
+ 0x219F, # upwards two headed arrow
+ 0x21A1, # downwards two headed arrow
+ 0x21A5, # upwards arrow from bar
+ 0x21A7, # downwards arrow from bar
+ 0x21A8, # up down arrow with base
+]
+for i in range(0, 1 + nvariants + 1):
+ s = (i + 1) * em / 10
+
+ g = f.createChar(hstretchy[i])
+ p = g.glyphPen()
+ p.moveTo(0, -em / 10)
+ p.lineTo(0, em / 10)
+ p.lineTo(s, em / 10)
+ p.lineTo(s, -em / 10)
+ p.closePath()
+ g.width = s
+
+ g = f.createChar(vstretchy[i])
+ p = g.glyphPen()
+ p.moveTo(0, 0)
+ p.lineTo(0, s)
+ p.lineTo(2 * em / 10, s)
+ p.lineTo(2 * em / 10, 0)
+ p.closePath()
+ g.width = 2 * em / 10
+
+# hstretchy[0] and vstretchy[0] have all the variants and the components. The others only have one of them.
+s = em * nvariants
+
+f[hstretchy[0]].horizontalVariants = "uni219C h0 h1 h2"
+f[hstretchy[0]].horizontalComponents = (
+ ("left", False, 0, 0, s),
+ ("h2", True, 0, 0, s),
+ ("hmid", False, 0, 0, 2 * s),
+ ("h2", True, 0, 0, s),
+ ("right", False, 0, 0, s),
+)
+
+f[hstretchy[1]].horizontalVariants = "uni219D h0"
+f[hstretchy[2]].horizontalVariants = "uni219E h1"
+f[hstretchy[3]].horizontalVariants = "uni21A0 h2"
+f[hstretchy[4]].horizontalVariants = "uni21A2 h2"
+f[hstretchy[4]].horizontalComponents = f[hstretchy[0]].horizontalComponents
+
+f[vstretchy[0]].verticalVariants = "uni219F v0 v1 v2"
+f[vstretchy[0]].verticalComponents = (
+ ("bottom", False, 0, 0, s),
+ ("v2", True, 0, 0, s),
+ ("vmid", False, 0, 0, 2 * s),
+ ("v2", True, 0, 0, s),
+ ("top", False, 0, 0, s),
+)
+
+f[vstretchy[1]].verticalVariants = "uni21A1 v0"
+f[vstretchy[2]].verticalVariants = "uni21A5 v1"
+f[vstretchy[3]].verticalVariants = "uni21A7 v2"
+f[vstretchy[4]].verticalVariants = "uni21A8"
+f[vstretchy[4]].verticalComponents = f[vstretchy[0]].verticalComponents
+
+################################################################################
+# Testing DisplayOperatorMinHeight
+f.math.DisplayOperatorMinHeight = 8 * em
+largeop = [0x2A1B, 0x2A1C] # integral with overbar/underbar
+
+# Draw boxes of size 1, 2, 7, 8, 9em.
+for i in [1, 2, 7, 8, 9]:
+ s = em * i
+ if i == 1 or i == 2:
+ g = f.createChar(largeop[i - 1])
+ else:
+ g = f.createChar(-1, "L%d" % i)
+ p = g.glyphPen()
+ p.moveTo(0, 0)
+ p.lineTo(0, s)
+ p.lineTo(s, s)
+ p.lineTo(s, 0)
+ p.closePath()
+ g.width = s
+
+f[largeop[0]].verticalVariants = "uni2A1B L7 L8 L9"
+f[largeop[1]].verticalVariants = "uni2A1C L8"
+
+saveMathFont(f)
+
+################################################################################
+# Testing AxisHeight
+f = newMathFont("axis-height-1")
+f.math.AxisHeight = 0
+createSquareGlyph(f, ord("+"))
+saveMathFont(f)
+
+f = newMathFont("axis-height-2")
+f.math.AxisHeight = 20 * em
+createSquareGlyph(f, ord("+"))
+saveMathFont(f)
+
+################################################################################
+# Testing Limits Parameters
+f = newMathFont("limits-5")
+f.math.UpperLimitGapMin = 0
+f.math.UpperLimitBaselineRiseMin = 0
+f.math.LowerLimitGapMin = 0
+f.math.LowerLimitBaselineDropMin = 0
+f.math.AccentBaseHeight = 6 * em
+f.math.FlattenedAccentBaseHeight = 3 * em
+createSquareGlyph(f, ord("~"))
+saveMathFont(f)
+
+f = newMathFont("dtls-1")
+createSquareGlyph(f, ord("a"))
+createLLTriangleGlyph(f, ord("b"))
+createSquareGlyph(f, ord("c"))
+createDiamondGlyph(f, 0x1D51E) # mathvariant=fraktur a
+createURTriangleGlyph(f, 0x1D51F) # mathvariant=fraktur b
+createDiamondGlyph(f, 0x1D520) # mathvariant=fraktur c
+f.addLookup("gsub", "gsub_single", (), (("dtls", (("latn", ("dflt")),)),))
+f.addLookupSubtable("gsub", "gsub_n")
+glyph = f["a"]
+glyph.addPosSub("gsub_n", "b")
+glyph2 = f[0x1D51F]
+glyph2.glyphname = "urtriangle"
+glyph3 = f[0x1D51E]
+glyph3.addPosSub("gsub_n", "urtriangle")
+saveMathFont(f)
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/limits-5.otf b/testing/web-platform/mozilla/tests/mathml/fonts/limits-5.otf
new file mode 100644
index 0000000000..fb8e9ec4fc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/limits-5.otf
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1-ref.html
new file mode 100644
index 0000000000..d591150929
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<head>
+
+ <!-- This font only has glyphs defined for 'A', 'B', 'C' and 'D', and is
+ designed purely for testing ssty and OpenType 'math' script
+ functionality
+ The glyphs for 'A' and 'D' are identical, the difference between them is
+ that 'A' supports the ssty font feature.
+ 'A' with ssty = 1 maps to 'B'
+ 'A' with ssty = 2 maps to 'C'
+ The difference between this font and ssty.woff is that the font feature
+ is contained within the OpenType 'math' script. -->
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "mathssty";
+ src: url("mathssty.woff");
+ }
+ </style>
+</head>
+<body>
+
+ <!-- Demonstrate that it has no effect outside MathML -->
+ <div style="font-family: 'mathssty';">D</div>
+ <div style="font-family: 'mathssty';">D</div>
+
+ <!-- Demonstrate that it works within MathML -->
+ <math>
+ <mstyle style="font-family: 'mathssty';">
+ <mrow>
+ <mo>D</mo>
+ <mo>B</mo>
+ <mo>C</mo>
+ </mrow>
+ </mstyle>
+ </math>
+ <p>
+ <!-- verify it works for the other elements except mtext -->
+ <math>
+ <mstyle style="font-family: 'mathssty';">
+ <mi mathvariant="normal">C</mi>
+ <mn>C</mn>
+ <mtext>D</mtext>
+ </mstyle>
+ </math>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1.html b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1.html
new file mode 100644
index 0000000000..808c27d030
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<head>
+ <link rel="match" href="mathscript-1-ref.html"/>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "mathssty";
+ src: url("mathssty.woff");
+ }
+ </style>
+</head>
+<body>
+
+ <!-- Demonstrate that it has no effect outside MathML -->
+ <div style="font-family: 'mathssty';
+ font-feature-settings: 'ssty' 1">A</div>
+ <div style="font-family: 'mathssty';
+ font-feature-settings: 'ssty' 2">A</div>
+
+ <!-- Demonstrate that it works within MathML -->
+ <math>
+ <mstyle style="font-family: 'mathssty';">
+ <mrow>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo style="font-feature-settings: 'ssty' 1">A</mo>
+ <mo style="font-feature-settings: 'ssty' 2">A</mo>
+ </mrow>
+ </mstyle>
+ </math>
+ <p>
+ <!-- verify it works for the other elements except mtext -->
+ <math>
+ <mstyle style="font-family: 'mathssty'; font-feature-settings: 'ssty' 2">
+ <mi mathvariant="normal">A</mi>
+ <mn>A</mn>
+ <mtext>A</mtext>
+ </mstyle>
+ </math>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2-ref.html
new file mode 100644
index 0000000000..6681292474
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dynamic OpenType 'math' script tests</title>
+ <!-- See mathscript-1-ref.html for an explanation of this font -->
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "mathssty";
+ src: url("mathssty.woff");
+ }
+ </style>
+ </head>
+ <body>
+
+ <!-- Demonstrate that it has no effect outside MathML -->
+ <div style="font-family: 'mathssty';" >DD</div>
+
+ <!-- Demonstrate that it works within MathML -->
+ <math>
+ <mstyle style="font-family: 'mathssty';">
+ <mo>CC</mo>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2.html b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2.html
new file mode 100644
index 0000000000..1a1ea1599e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/mathscript-2.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Dynamic OpenType 'math' script tests</title>
+ <link rel="match" href="mathscript-2-ref.html"/>
+ <!-- See mathscript-1-ref.html for an explanation of this font -->
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "mathssty";
+ src: url("mathssty.woff");
+ }
+ </style>
+ </head>
+ <body>
+
+ <!-- Demonstrate that it has no effect outside MathML -->
+ <div style="font-family: 'mathssty';
+ font-feature-settings: 'ssty' " id="div0">A</div>
+
+ <!-- Demonstrate that it works within MathML -->
+ <math>
+ <mstyle style="font-family: 'mathssty'; font-feature-settings: 'ssty' 2">
+ <mo id="mo0">A</mo>
+ </mstyle>
+ </math>
+
+ <script>
+ function doTest()
+ {
+ // Does nothing to non-MathML
+ document.getElementById("div0").appendChild(document.createTextNode("A"));
+ // Does something to MathML
+ document.getElementById("mo0").appendChild(document.createTextNode("A"));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/mathssty.woff b/testing/web-platform/mozilla/tests/mathml/fonts/mathssty.woff
new file mode 100644
index 0000000000..eb6a667753
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/mathssty.woff
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/opentype-axis-height.html b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-axis-height.html
new file mode 100644
index 0000000000..8a7050ad4b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-axis-height.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - axis-height</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: axis-height-1;
+ src: url(axis-height-1.otf);
+ }
+ @font-face {
+ font-family: axis-height-2;
+ src: url(axis-height-2.otf);
+ }
+ </style>
+ <script type="application/javascript">
+ setup({explicit_done : true});
+
+ var epsilon = 5;
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ function doTest() {
+ test(function() {
+ assert_approx_equals(getBox("plus1").top - getBox("plus2").top, 10 * 20, epsilon);
+ }, "AxisHeight");
+ done();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <p>
+ <math style="font-family: axis-height-1;">
+ <mo id="plus1">+</mo>
+ </math>
+ <math style="font-family: axis-height-2;">
+ <mo id="plus2">+</mo>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/opentype-limits.html b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-limits.html
new file mode 100644
index 0000000000..575dfefc11
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-limits.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Open Type MATH - limits</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=961365"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ math {
+ font-size: 10px;
+ }
+ @font-face {
+ font-family: limits-5;
+ src: url(limits-5.otf);
+ }
+ </style>
+ <script type="text/javascript">
+ setup({explicit_done : true});
+
+ var epsilon = 5;
+
+ function getBox(aId) {
+ return document.getElementById(aId).getBoundingClientRect();
+ }
+
+ function doTest() {
+ test(function() {
+ assert_approx_equals(getBox("base9").top - getBox("over9").bottom,
+ (6 - 2) * 10, epsilon);
+ assert_approx_equals(getBox("base10").top - getBox("over10").bottom,
+ (6 - 2) * 10, epsilon);
+ }, "AccentBaseHeight");
+ done();
+ }
+ </script>
+ </head>
+ <body onload="doTest()">
+
+ <p>
+ <math style="font-family: limits-5;" displaystyle="true">
+ <mspace id="ref5" height="1em" width="1em" mathbackground="green"/>
+ </math>
+ <math style="font-family: limits-5;" displaystyle="true">
+ <mover>
+ <mspace id="base9" height="2em" width="2em" mathbackground="blue"/>
+ <mo id="over9" stretchy="false">~</mo>
+ </mover>
+ </math>
+ <math style="font-family: limits-5;" displaystyle="true">
+ <munderover>
+ <mspace id="base10" height="2em" width="2em" mathbackground="blue"/>
+ <mspace id="under10" height="1em" width="1em" mathbackground="red"/>
+ <mo id="over10" stretchy="false">~</mo>
+ </munderover>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy-ref.html
new file mode 100644
index 0000000000..7de372170d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy-ref.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Open Type MATH - stretchy operator</title>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ @font-face {
+ font-family: stretchy;
+ src: url(stretchy.otf);
+ }
+ math {
+ font-family: stretchy;
+ font-size: 10px;
+ }
+ </style>
+ </head>
+ <body>
+
+<!--
+hstretchy = [
+ 0x219C, # leftwards wave arrow
+ 0x219D, # rightwards wave arrow
+ 0x219E, # leftwards two headed arrow
+ 0x21A0, # rightwards two headed arrow
+ 0x21A2 # leftwards arrow with tail
+]
+vstretchy = [
+ 0x219F, # upwards two headed arrow
+ 0x21A1, # downwards two headed arrow
+ 0x21A5, # upwards arrow from bar
+ 0x21A7, # downwards arrow from bar
+ 0x21A8 # up down arrow with base
+]
+
+hstretchy[0] and vstretchy[0] have all the variants and the components. The others only have one of them.
+-->
+
+ <p>
+ <math>
+ <mstyle>
+ <mover><mo stretchy="true">&#x219D;</mo><mspace width="1em" height="1px" mathbackground="red"/></mover>
+ <mover><mo stretchy="true">&#x219E;</mo><mspace width="2em" height="1px" mathbackground="red"/></mover>
+ <mover><mo stretchy="true">&#x21A0;</mo><mspace width="3em" height="1px" mathbackground="red"/></mover>
+ <mover><mo stretchy="true">&#x21A2;</mo><mspace width="15em" height="1px" mathbackground="red"/></mover>
+ </mstyle>
+ </math>
+ </p>
+
+ <p>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="1em">&#x21A1;</mo></mrow></math>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="2em">&#x21A5;</mo></mrow></math>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="3em">&#x21A7;</mo></mrow></math>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="15em">&#x21A8;</mo></mrow></math>
+ </p>
+
+<!--
+DisplayOperatorMinHeight = 8em
+largeop = [0x2A1B, 0x2A1C] # integral with overbar/underbar
+largeop[0] has variants of size 7, 8, 9em
+largeop[1] has one variant of size 8em.
+-->
+ <p>
+ <math displaystyle="true">
+ <mrow><mo>&#x2A1C;</mo></mrow>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy.html b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy.html
new file mode 100644
index 0000000000..50bccf9b02
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/opentype-stretchy.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Open Type MATH - stretchy operator</title>
+ <link rel="match" href="opentype-stretchy-ref.html"/>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ @font-face {
+ font-family: stretchy;
+ src: url(stretchy.otf);
+ }
+ math {
+ font-family: stretchy;
+ font-size: 10px;
+ }
+ </style>
+ </head>
+ <body>
+
+<!--
+hstretchy = [
+ 0x219C, # leftwards wave arrow
+ 0x219D, # rightwards wave arrow
+ 0x219E, # leftwards two headed arrow
+ 0x21A0, # rightwards two headed arrow
+ 0x21A2 # leftwards arrow with tail
+]
+vstretchy = [
+ 0x219F, # upwards two headed arrow
+ 0x21A1, # downwards two headed arrow
+ 0x21A5, # upwards arrow from bar
+ 0x21A7, # downwards arrow from bar
+ 0x21A8 # up down arrow with base
+]
+
+hstretchy[0] and vstretchy[0] have all the variants and the components. The others only have one of them.
+-->
+
+ <p>
+ <math>
+ <mstyle>
+ <mover><mo stretchy="true">&#x219C;</mo><mspace width="1em" height="1px" mathbackground="red"/></mover>
+ <mover><mo stretchy="true">&#x219C;</mo><mspace width="2em" height="1px" mathbackground="red"/></mover>
+ <mover><mo stretchy="true">&#x219C;</mo><mspace width="3em" height="1px" mathbackground="red"/></mover>
+ <mover><mo stretchy="true">&#x219C;</mo><mspace width="15em" height="1px" mathbackground="red"/></mover>
+ </mstyle>
+ </math>
+ </p>
+
+ <p>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="1em">&#x219F;</mo></mrow></math>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="2em">&#x219F;</mo></mrow></math>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="3em">&#x219F;</mo></mrow></math>
+ <math><mrow><mo symmetric="false" stretchy="true" minsize="15em">&#x219F;</mo></mrow></math>
+ </p>
+
+<!--
+DisplayOperatorMinHeight = 8em
+largeop = [0x2A1B, 0x2A1C] # integral with overbar/underbar
+largeop[0] has variants of size 7, 8, 9em
+largeop[1] has one variant of size 8em.
+-->
+ <p>
+ <math displaystyle="true">
+ <mrow><mo>&#x2A1B;</mo></mrow>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/ssty-1-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-1-ref.html
new file mode 100644
index 0000000000..d01b0e5b08
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-1-ref.html
@@ -0,0 +1,337 @@
+<!DOCTYPE html>
+<head>
+
+ <!-- This font only has glyphs defined for 'A', 'B', 'C' and 'D', and is
+ designed purely for testing ssty functionality
+ The glyphs for 'A' and 'D' are identical, the difference between them is
+ that 'A' supports the ssty font feature.
+ 'A' with ssty = 1 maps to 'B'
+ 'A' with ssty = 2 maps to 'C'-->
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "sstyfont";
+ src: url("ssty.woff");
+ }
+ </style>
+</head>
+<body>
+
+ <!-- Test whether the ssty font feature setting is used appropriately for
+ supscripts et al.
+ Assumes kMathMLDefaultScriptSizeMultiplier is 0.71-->
+
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+ <msup>
+ <mo>D</mo>
+ <msup>
+ <mo>B</mo>
+ <msup>
+ <mo>C</mo>
+ <mo>C</mo> <!-- ssty value capped at 2 -->
+ </msup>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>D</mo>
+ <msub>
+ <mo>B</mo>
+ <msub>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msub>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>D</mo>
+ <msubsup>
+ <mo>B</mo>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+ <msubsup>
+ <mo>B</mo>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>D</mo>
+ <mmultiscripts>
+ <mo>B</mo>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>B</mo>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <!-- Automatically set ssty ignores user set scriptlevel -->
+ <math>
+ <mstyle style="font-family: 'sstyfont';" scriptlevel="-3">
+ <msup>
+ <mo>D</mo>
+ <msup>
+ <mo>B</mo>
+ <msup>
+ <mo>C</mo>
+ <mo>C</mo> <!-- ssty value capped at 2 -->
+ </msup>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>D</mo>
+ <msub>
+ <mo>B</mo>
+ <msub>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msub>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>D</mo>
+ <msubsup>
+ <mo>B</mo>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+ <msubsup>
+ <mo>B</mo>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>D</mo>
+ <mmultiscripts>
+ <mo>B</mo>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>B</mo>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <!-- Automatically set ssty ignores user set scriptlevel -->
+ <math>
+ <mstyle style="font-family: 'sstyfont';" scriptlevel="1">
+ <msup>
+ <mo>D</mo>
+ <msup>
+ <mo>B</mo>
+ <msup>
+ <mo>C</mo>
+ <mo>C</mo> <!-- ssty value capped at 2 -->
+ </msup>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>D</mo>
+ <msub>
+ <mo>B</mo>
+ <msub>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msub>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>D</mo>
+ <msubsup>
+ <mo>B</mo>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+ <msubsup>
+ <mo>B</mo>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>D</mo>
+ <mmultiscripts>
+ <mo>B</mo>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>B</mo>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>C</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <!-- User set ssty font feature setting overrides automatically set ssty, but
+ only for affected elements -->
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+ <msup>
+ <mo>D</mo>
+ <msup>
+ <mo>D</mo>
+ <mo>C</mo>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>D</mo>
+ <msub>
+ <mo>D</mo>
+ <mo>C</mo>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>D</mo>
+ <msubsup>
+ <mo>D</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ <msubsup>
+ <mo>D</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>D</mo>
+ <mmultiscripts>
+ <mo>D</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>D</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/ssty-1.html b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-1.html
new file mode 100644
index 0000000000..f918b9c6d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-1.html
@@ -0,0 +1,325 @@
+<!DOCTYPE html>
+<head>
+ <link rel="match" href="ssty-1-ref.html"/>
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "sstyfont";
+ src: url("ssty.woff");
+ }
+ </style>
+</head>
+<body>
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo>A</mo>
+ <mo>A</mo> <!-- ssty value capped at 2 -->
+ </msup>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msub>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <!-- Automatically set ssty ignores scriptlevel -->
+ <math>
+ <mstyle style="font-family: 'sstyfont';" scriptlevel="-3">
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo>A</mo>
+ <mo>A</mo> <!-- ssty value capped at 2 -->
+ </msup>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msub>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <!-- Automatically set ssty ignores scriptlevel -->
+ <math>
+ <mstyle style="font-family: 'sstyfont';" scriptlevel="1">
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo>A</mo>
+ <mo>A</mo> <!-- ssty value capped at 2 -->
+ </msup>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msub>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <!-- User set ssty font feature setting overrides automatically set ssty -->
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+ <msup>
+ <mo>A</mo>
+ <msup>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo>A</mo>
+ </msup>
+ </msup>
+
+ <msub>
+ <mo>A</mo>
+ <msub>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo>A</mo>
+ </msub>
+ </msub>
+
+ <msubsup>
+ <mo>A</mo>
+ <msubsup>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ <msubsup>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </msubsup>
+ </msubsup>
+
+ <mmultiscripts>
+ <mo>A</mo>
+ <mmultiscripts>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ <mmultiscripts>
+ <mo style="font-feature-settings: 'ssty' 0">A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mmultiscripts>
+ </mmultiscripts>
+ </mstyle>
+ </math>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/ssty-2-ref.html b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-2-ref.html
new file mode 100644
index 0000000000..c11c328f38
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-2-ref.html
@@ -0,0 +1,275 @@
+<!DOCTYPE html>
+<head>
+ <!-- See ssty-1-ref.html for an explanation of this font -->
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "sstyfont";
+ src: url("ssty.woff");
+ }
+ </style>
+</head>
+<body>
+ <!-- Test whether the ssty font feature setting is used appropriately for
+ mroot, mfrac, munderover et al.
+ Assumes kMathMLDefaultScriptSizeMultiplier is 0.71-->
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+ <mroot>
+ <mo>D</mo>
+ <mo>C</mo>
+ </mroot>
+
+ <mfrac>
+ <mo>B</mo>
+ <mo>B</mo>
+ </mfrac>
+
+ <mfrac>
+ <mfrac>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mfrac>
+ <mfrac>
+ <mo>C</mo>
+ <mo>C</mo>
+ </mfrac>
+ </mfrac>
+
+ <mfrac>
+ <mroot>
+ <mo>B</mo>
+ <mo>C</mo>
+ </mroot>
+ <mo>B</mo>
+ </mfrac>
+
+ <mover>
+ <mo>D</mo>
+ <mover>
+ <mo>B</mo>
+ <mo>C</mo>
+ </mover>
+ </mover>
+
+ <munder>
+ <mo>D</mo>
+ <munder>
+ <mo>B</mo>
+ <mo>C</mo>
+ </munder>
+ </munder>
+
+ <munderover>
+ <mo>D</mo>
+ <munderover>
+ <mo>B</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </munderover>
+ <munderover>
+ <mo>B</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </munderover>
+ </munderover>
+
+ </mstyle>
+ </math>
+
+ <p>
+ <!-- ssty font feature not set on mfrac et al when displaystyle is
+ set (still allowed on mroot, mover et al.)-->
+ <math>
+ <mstyle style="font-family: 'sstyfont';" displaystyle="true">
+ <mroot>
+ <mo>D</mo>
+ <mo>C</mo>
+ </mroot>
+
+ <mfrac>
+ <mo>D</mo>
+ <mo>D</mo>
+ </mfrac>
+
+ <mfrac>
+ <mfrac>
+ <!-- ssty gets set as script level incremented because displaystyle
+ is now false -->
+ <mo>B</mo>
+ <mo>B</mo>
+ </mfrac>
+ <mfrac>
+ <mo>B</mo>
+ <mo>B</mo>
+ </mfrac>
+ </mfrac>
+
+ <mfrac>
+ <mroot>
+ <mo>D</mo>
+ <mo>C</mo>
+ </mroot>
+ <mo>D</mo>
+ </mfrac>
+
+ <mover>
+ <mo>D</mo>
+ <mover>
+ <mo>B</mo>
+ <mo>C</mo>
+ </mover>
+ </mover>
+
+ <munder>
+ <mo>D</mo>
+ <munder>
+ <mo>B</mo>
+ <mo>C</mo>
+ </munder>
+ </munder>
+
+ <munderover>
+ <mo>D</mo>
+ <munderover>
+ <mo>B</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </munderover>
+ <munderover>
+ <mo>B</mo>
+ <mo>C</mo>
+ <mo>C</mo>
+ </munderover>
+ </munderover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <!-- scriptlevel is not incremented when accent for overframes and
+ accentunder for underframes are true, so there shouldn't be a
+ change in the ssty value -->
+ <mstyle style="font-family: 'sstyfont';">
+ <mover accent="true">
+ <mo>D</mo>
+ <mover accent="true">
+ <mo>D</mo>
+ <mo>D</mo>
+ </mover>
+ </mover>
+
+ <munder accentunder="true">
+ <mo>D</mo>
+ <munder accentunder="true">
+ <mo>D</mo>
+ <mo>D</mo>
+ </munder>
+ </munder>
+
+ <munderover accentunder="true" accent="true">
+ <mo>D</mo>
+ <munderover accentunder="true" accent="true">
+ <mo>D</mo>
+ <mo>D</mo>
+ <mo>D</mo>
+ </munderover>
+ <munderover accentunder="true" accent="true">
+ <mo>D</mo>
+ <mo>D</mo>
+ <mo>D</mo>
+ </munderover>
+ </munderover>
+
+ <mover accent="true">
+ <mo>D</mo>
+ <mover accent="false">
+ <mo>D</mo>
+ <mo>B</mo>
+ </mover>
+ </mover>
+
+ <munder accentunder="true">
+ <mo>D</mo>
+ <munder accentunder="false">
+ <mo>D</mo>
+ <mo>B</mo>
+ </munder>
+ </munder>
+
+ <munderover accentunder="true" accent="true">
+ <mo>D</mo>
+ <munderover accentunder="false" accent="false">
+ <mo>D</mo>
+ <mo>B</mo>
+ <mo>B</mo>
+ </munderover>
+ <munderover accentunder="false" accent="false">
+ <mo>D</mo>
+ <mo>B</mo>
+ <mo>B</mo>
+ </munderover>
+ </munderover>
+
+ <mover accentunder="false" accent="false">
+ <mo>D</mo>
+ <mover accentunder="true" accent="true">
+ <mo>B</mo>
+ <mo>B</mo>
+ </mover>
+ </mover>
+
+ <munder accentunder="false" accent="false">
+ <mo>D</mo>
+ <munder accentunder="true" accent="true">
+ <mo>B</mo>
+ <mo>B</mo>
+ </munder>
+ </munder>
+
+ <munderover accentunder="false" accent="false">
+ <mo>D</mo>
+ <munderover accentunder="true" accent="true">
+ <mo>B</mo>
+ <mo>B</mo>
+ <mo>B</mo>
+ </munderover>
+ <munderover accentunder="true" accent="true">
+ <mo>B</mo>
+ <mo>B</mo>
+ <mo>B</mo>
+ </munderover>
+ </munderover>
+
+ <munderover accentunder="false" accent="true">
+ <mo>D</mo>
+ <munderover accentunder="false" accent="true">
+ <mo>B</mo>
+ <mo>C</mo>
+ <mo>B</mo>
+ </munderover>
+ <munderover accentunder="false" accent="true">
+ <mo>D</mo>
+ <mo>B</mo>
+ <mo>D</mo>
+ </munderover>
+ </munderover>
+
+ <munderover accentunder="true" accent="false">
+ <mo>D</mo>
+ <munderover accentunder="true" accent="false">
+ <mo>D</mo>
+ <mo>D</mo>
+ <mo>B</mo>
+ </munderover>
+ <munderover accentunder="true" accent="false">
+ <mo>B</mo>
+ <mo>B</mo>
+ <mo>C</mo>
+ </munderover>
+ </munderover>
+
+ </mstyle>
+ </math>
+
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/ssty-2.html b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-2.html
new file mode 100644
index 0000000000..c4ffe1bd8b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/ssty-2.html
@@ -0,0 +1,268 @@
+<!DOCTYPE html>
+<head>
+ <link rel="match" href="ssty-2-ref.html"/>
+ <!-- See ssty-1-ref.html for an explanation of this font -->
+ <style type="text/css" media="screen, print">
+ @font-face {
+ font-family: "sstyfont";
+ src: url("ssty.woff");
+ }
+ </style>
+</head>
+<body>
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+ <mroot>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mroot>
+
+ <mfrac>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mfrac>
+
+ <mfrac>
+ <mfrac>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mfrac>
+ <mfrac>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mfrac>
+ </mfrac>
+
+ <mfrac>
+ <mroot>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mroot>
+ <mo>A</mo>
+ </mfrac>
+
+ <mover>
+ <mo>A</mo>
+ <mover>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mover>
+ </mover>
+
+ <munder>
+ <mo>A</mo>
+ <munder>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munder>
+ </munder>
+
+ <munderover>
+ <mo>A</mo>
+ <munderover>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'sstyfont';" displaystyle="true">
+ <mroot>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mroot>
+
+ <mfrac>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mfrac>
+
+ <mfrac>
+ <mfrac>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mfrac>
+ <mfrac>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mfrac>
+ </mfrac>
+
+ <mfrac>
+ <mroot>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mroot>
+ <mo>A</mo>
+ </mfrac>
+
+ <mover>
+ <mo>A</mo>
+ <mover>
+ <mo>A</mo>
+ <mo>A</mo>
+ </mover>
+ </mover>
+
+ <munder>
+ <mo>A</mo>
+ <munder>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munder>
+ </munder>
+
+ <munderover>
+ <mo>A</mo>
+ <munderover>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover>
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle style="font-family: 'sstyfont';">
+
+ <mover accent="true">
+ <mo>A</mo>
+ <mover accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ </mover>
+ </mover>
+
+ <munder accentunder="true">
+ <mo>A</mo>
+ <munder accentunder="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ </munder>
+ </munder>
+
+ <munderover accentunder="true" accent="true">
+ <mo>A</mo>
+ <munderover accentunder="true" accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover accentunder="true" accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+
+ <mover accentunder="true" accent="true">
+ <mo>A</mo>
+ <mover accentunder="false" accent="false">
+ <mo>A</mo>
+ <mo>A</mo>
+ </mover>
+ </mover>
+
+ <munder accentunder="true" accent="true">
+ <mo>A</mo>
+ <munder accentunder="false" accent="false">
+ <mo>A</mo>
+ <mo>A</mo>
+ </munder>
+ </munder>
+
+ <munderover accentunder="true" accent="true">
+ <mo>A</mo>
+ <munderover accentunder="false" accent="false">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover accentunder="false" accent="false">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+
+ <mover accent="false">
+ <mo>A</mo>
+ <mover accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ </mover>
+ </mover>
+
+ <munder accentunder="false">
+ <mo>A</mo>
+ <munder accentunder="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ </munder>
+ </munder>
+
+ <munderover accentunder="false" accent="false">
+ <mo>A</mo>
+ <munderover accentunder="true" accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover accentunder="true" accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+
+ <munderover accentunder="false" accent="true">
+ <mo>A</mo>
+ <munderover accentunder="false" accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover accentunder="false" accent="true">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+
+ <munderover accentunder="true" accent="false">
+ <mo>A</mo>
+ <munderover accentunder="true" accent="false">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ <munderover accentunder="true" accent="false">
+ <mo>A</mo>
+ <mo>A</mo>
+ <mo>A</mo>
+ </munderover>
+ </munderover>
+
+ </mstyle>
+ </math>
+
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/ssty.woff b/testing/web-platform/mozilla/tests/mathml/fonts/ssty.woff
new file mode 100644
index 0000000000..19312c713d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/ssty.woff
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/fonts/stretchy.otf b/testing/web-platform/mozilla/tests/mathml/fonts/stretchy.otf
new file mode 100644
index 0000000000..f192de3463
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/fonts/stretchy.otf
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mathml/mathml-console-messages.html b/testing/web-platform/mozilla/tests/mathml/mathml-console-messages.html
new file mode 100644
index 0000000000..183d11feaa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathml-console-messages.html
@@ -0,0 +1,245 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test MathML console messages</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=553917"/>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=827713"/>
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1845461"/>
+<body>
+ <script>
+ const MessageLevel = {
+ ERROR: 0,
+ WARNING: 1,
+ };
+
+ function retrieveConsoleMessagesFor(markup) {
+ return new Promise(resolve => {
+ const iframe = document.createElement('iframe');
+ iframe.srcdoc = `<!DOCTYPE html>
+<script>let messages = [];
+ SpecialPowers.registerConsoleListener(msg => {
+ if (msg.message == "SENTINEL") {
+ window.parent.postMessage(messages);
+ } else if (msg.isScriptError) {
+ messages.push(msg);
+ }
+ });
+ window.addEventListener("load", () => SpecialPowers.postConsoleSentinel());
+<\/script>
+<body>${markup}</body>`;
+ window.addEventListener("message", event => {
+ iframe.remove();
+ resolve(event.data);
+ }, {once: true});
+ document.body.appendChild(iframe);
+ });
+ }
+
+ function testMessageForMarkup(markup, regexp, level) {
+ promise_test(async function() {
+ let messages = await retrieveConsoleMessagesFor(markup);
+
+ // Sometimes MathML messages are logged several times, so just
+ // ensure there is at least one.
+ assert_greater_than_equal(messages.length, 1);
+
+ // Compare against the regexp.
+ assert_regexp_match(messages[0].errorMessage, regexp);
+
+ // Check whether this is a warning or an error.
+ assert_equals(messages[0].isWarning, level == MessageLevel.WARNING);
+ }, `Message for ${markup}`);
+ }
+
+ function testNoMessageForMarkup(markup) {
+ promise_test(async function() {
+ let messages = await retrieveConsoleMessagesFor(markup);
+ assert_equals(messages.length, 0);
+ }, `No message for ${markup}`);
+ }
+
+ // ChildCountIncorrect
+ [
+ "mroot",
+ "msub",
+ "msup",
+ "mfrac",
+ "msubsup",
+ "munderover",
+ ].forEach(tag => {
+ testMessageForMarkup(
+ `<math><${tag}></${tag}></math>`,
+ new RegExp(`Incorrect number of children for <${tag}/>`),
+ MessageLevel.ERROR);
+ });
+
+ // AttributeParsingError
+ [
+ "width",
+ "height",
+ "voffset",
+ ].forEach(attribute => {
+ testMessageForMarkup(
+ `<math><mpadded ${attribute}="BAD!"></mpadded></math>`,
+ new RegExp(`Error in parsing the value ‘BAD!’ for ‘${attribute}’ attribute`),
+ MessageLevel.ERROR);
+ });
+
+ // LengthParsingError
+ [
+ '<math><mo rspace="2..0px">+</mo></math>',
+ '<math><mo minsize="1.5notaunit">+</mo></math>',
+ '<math><mspace width="2"/></math>',
+ '<math><mo lspace="BADlspace">+</mo></math>',
+ '<math><mspace height="BADheight"/></math>',
+ '<math><mspace depth="BADdepth"/></math>',
+ '<math><mfrac linethickness="thin"><mn>1</mn><mn>2</mn></mfrac></math>',
+ '<math><mfrac linethickness="medium"><mn>1</mn><mn>2</mn></mfrac></math>',
+ '<math><mfrac linethickness="thick"><mn>1</mn><mn>2</mn></mfrac></math>',
+ '<math><mstyle mathsize="small"></mstyle></math>',
+ '<math><mstyle mathsize="normal"></mstyle></math>',
+ '<math><mstyle mathsize="big"></mstyle></math>',
+ '<math><mspace width="12345."/></math>',
+ '<math><mo minsize="17">+</mo></math>',
+ ].forEach(markup => {
+ const value = /="([a-zA-Z0-9.]+)"/.exec(markup)[1];
+ testMessageForMarkup(
+ markup,
+ new RegExp(`Error in parsing MathML attribute value ‘${value}’`),
+ MessageLevel.ERROR);
+ });
+
+ // MathML_DeprecatedMathSpaceValueWarning
+ [
+ '<math><mspace width="mediummathspace"></mspace></math>',
+ '<math><mspace width="negativemediummathspace"></mspace></math>',
+ '<math><mspace width="negativethickmathspace"></mspace></math>',
+ '<math><mspace width="negativethinmathspace"></mspace></math>',
+ '<math><mspace width="negativeverythickmathspace"></mspace></math>',
+ '<math><mspace width="negativeverythinmathspace"></mspace></math>',
+ '<math><mspace width="negativeveryverythickmathspace"></mspace></math>',
+ '<math><mspace width="negativeveryverythinmathspace"></mspace></math>',
+ '<math><mspace width="thickmathspace"></mspace></math>',
+ '<math><mspace width="thinmathspace"></mspace></math>',
+ '<math><mspace width="verythickmathspace"></mspace></math>',
+ '<math><mspace width="verythinmathspace"></mspace></math>',
+ '<math><mspace width="veryverythickmathspace"></mspace></math>',
+ '<math><mspace width="veryverythinmathspace"></mspace></math>',
+ ].forEach(markup => {
+ const value = /="([a-zA-Z0-9.]+)"/.exec(markup)[1];
+ testMessageForMarkup(
+ markup,
+ new RegExp(`MathML length value “${value}” is deprecated`),
+ MessageLevel.WARNING);
+ });
+
+ // InvalidChild
+ [
+ `<math>
+ <msubsup>
+ <mprescripts/>
+ </msubsup>
+ </math>`,
+ `<math>
+ <msubsup>
+ <mprescripts/>
+ <mprescripts/>
+ </msubsup>
+ </math>`,
+ `<math>
+ <msub>
+ <mtext>a</mtext>
+ <mprescripts/>
+ <mtext>a</mtext>
+ <mprescripts/>
+ </msub>
+ </math>`,
+ '<math><msub><mn>0</mn><mprescripts/></msub></math>',
+ '<math><msup><mn>0</mn><mprescripts/></msup></math>',
+ '<math><msubsup><mn>0</mn><mn>1</mn><mprescripts/></msubsup></math>',
+ ].forEach(markup => {
+ const tag = /<math>\s*<([a-z]+)>/.exec(markup)[1];
+ testMessageForMarkup(
+ markup,
+ new RegExp(`<mprescripts> is not allowed as a child of <${tag}>`),
+ MessageLevel.ERROR);
+ });
+
+ // NoBase
+ testMessageForMarkup(
+ `<math><mmultiscripts></mmultiscripts></math>`,
+ /Expected exactly one Base element/,
+ MessageLevel.ERROR
+ );
+
+ // AttributeParsingErrorNoTag
+ testMessageForMarkup(
+ `<math scriptlevel="BAD!"></math>`,
+ /Error in parsing the value ‘BAD!’ for ‘scriptlevel’ attribute/,
+ MessageLevel.ERROR
+ );
+
+ // DuplicateMprescripts
+ testMessageForMarkup(
+ `<math>
+ <mmultiscripts>
+ <mprescripts/>
+ <mprescripts/>
+ </mmultiscripts>
+ </math>`,
+ /More than one <mprescripts\/>/,
+ MessageLevel.ERROR);
+
+ // SubSupMismatch
+ [
+ `<math>
+ <mmultiscripts>
+ <mi>x</mi>
+ <mi>y</mi>
+ </mmultiscripts>
+ </math>`,
+ `<math>
+ <mmultiscripts>
+ <mtext>b</mtext>
+ <mtext>c</mtext>
+ <mprescripts/>
+ <mtext>a</mtext>
+ </mmultiscripts>
+ </math>`,
+ ].forEach(markup => {
+ testMessageForMarkup(
+ markup,
+ /Incomplete subscript\/superscript pair/,
+ MessageLevel.ERROR);
+ });
+
+
+ // MathML_DeprecatedMathVariantWarning
+ testNoMessageForMarkup('<math><mi mathvariant="normal">A</mi></math>');
+ [
+ "bold",
+ "italic",
+ "bold-italic",
+ "script",
+ "bold-script",
+ "fraktur",
+ "double-struck",
+ "bold-fraktur",
+ "sans-serif",
+ "bold-sans-serif",
+ "sans-serif-italic",
+ "sans-serif-bold-italic",
+ "monospace",
+ "initial",
+ "tailed",
+ "looped",
+ "stretched"
+ ].forEach((value) => {
+ testMessageForMarkup(
+ `<math><mi mathvariant="${value}">A</mi></math>`,
+ new RegExp(`mathvariant='${value}'” .* deprecated`),
+ MessageLevel.WARNING);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathml-type-supported-ref.xml b/testing/web-platform/mozilla/tests/mathml/mathml-type-supported-ref.xml
new file mode 100644
index 0000000000..1a471a2c6e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathml-type-supported-ref.xml
@@ -0,0 +1,4 @@
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mspace style="position: absolute; top: 0; left: 0; background: green;"
+ width="100px" height="100px" depth="0"/>
+</math>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathml-type-supported.xhtml b/testing/web-platform/mozilla/tests/mathml/mathml-type-supported.xhtml
new file mode 100644
index 0000000000..50a7dd2130
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathml-type-supported.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Test mime type application/mathml+xml</title>
+ <link rel="match" href="mathml-type-supported-ref.xml"/>
+ <link rel="help" href="https://github.com/w3c/mathml-core/issues/204"/>
+</head>
+<body>
+ <object type="application/mathml+xml" data="mathml-type-supported-ref.xml"
+ style="position: absolute; top: 0; left: 0; background: red;
+ width: 100px; height: 100px;"/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace-ref.html b/testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace-ref.html
new file mode 100644
index 0000000000..235ef69a18
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace-ref.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head></head>
+ <body>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.0555555556em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.111111111em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.166666667em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.222222222em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.277777778em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.333333333em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="-0.388888889em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace.html b/testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace.html
new file mode 100644
index 0000000000..3ab0812a4e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathspace_names/negative-namedspace.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="match" href="negative-namedspace-ref.html"/>
+ </head>
+ <body>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativeveryverythinmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativeverythinmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativethinmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativemediummathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativethickmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativeverythickmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="negativeveryverythickmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace-ref.html b/testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace-ref.html
new file mode 100644
index 0000000000..00c89f0c85
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace-ref.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head></head>
+ <body>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.0555555556em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.111111111em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.166666667em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.222222222em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.277777778em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.333333333em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="0.388888889em"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace.html b/testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace.html
new file mode 100644
index 0000000000..08b45eadef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathspace_names/positive-namedspace.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="match" href="positive-namedspace-ref.html"/>
+ </head>
+ <body>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="veryverythinmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="verythinmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="thinmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="mediummathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="thickmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="verythickmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mi>x</mi> <mspace width="veryverythickmathspace"></mspace> <mi>y</mi>
+ </mrow>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a-ref.html
new file mode 100644
index 0000000000..b70be9312f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a-ref.html
@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext>ABCDEFGHIJKLMNOPQRSTUVWXYZ</mtext>
+ <mtext>abcdefghijklmnopqrstuvwxyz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d400;&#x1d401;&#x1d402;&#x1d403;&#x1d404;&#x1d405;
+ &#x1d406;&#x1d407;&#x1d408;&#x1d409;&#x1d40a;&#x1d40b;
+ &#x1d40c;&#x1d40d;&#x1d40e;&#x1d40f;&#x1d410;&#x1d411;
+ &#x1d412;&#x1d413;&#x1d414;&#x1d415;&#x1d416;&#x1d417;
+ &#x1d418;&#x1d419;
+ &#x1d41a;&#x1d41b;&#x1d41c;&#x1d41d;&#x1d41e;&#x1d41f;
+ &#x1d420;&#x1d421;&#x1d422;&#x1d423;&#x1d424;&#x1d425;
+ &#x1d426;&#x1d427;&#x1d428;&#x1d429;&#x1d42a;&#x1d42b;
+ &#x1d42c;&#x1d42d;&#x1d42e;&#x1d42f;&#x1d430;&#x1d431;
+ &#x1d432;&#x1d433;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d434;&#x1d435;&#x1d436;&#x1d437;&#x1d438;&#x1d439;
+ &#x1d43a;&#x1d43b;&#x1d43c;&#x1d43d;&#x1d43e;&#x1d43f;
+ &#x1d440;&#x1d441;&#x1d442;&#x1d443;&#x1d444;&#x1d445;
+ &#x1d446;&#x1d447;&#x1d448;&#x1d449;&#x1d44a;&#x1d44b;
+ &#x1d44c;&#x1d44d;
+ &#x1d44e;&#x1d44f;&#x1d450;&#x1d451;&#x1d452;&#x1d453;
+ &#x1d454;&#x210e;&#x1d456;&#x1d457;&#x1d458;&#x1d459;
+ &#x1d45a;&#x1d45b;&#x1d45c;&#x1d45d;&#x1d45e;&#x1d45f;
+ &#x1d460;&#x1d461;&#x1d462;&#x1d463;&#x1d464;&#x1d465;
+ &#x1d466;&#x1d467;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d468;&#x1d469;&#x1d46a;&#x1d46b;&#x1d46c;&#x1d46d;
+ &#x1d46e;&#x1d46f;&#x1d470;&#x1d471;&#x1d472;&#x1d473;
+ &#x1d474;&#x1d475;&#x1d476;&#x1d477;&#x1d478;&#x1d479;
+ &#x1d47a;&#x1d47b;&#x1d47c;&#x1d47d;&#x1d47e;&#x1d47f;
+ &#x1d480;&#x1d481;
+ &#x1d482;&#x1d483;&#x1d484;&#x1d485;&#x1d486;&#x1d487;
+ &#x1d488;&#x1d489;&#x1d48a;&#x1d48b;&#x1d48c;&#x1d48d;
+ &#x1d48e;&#x1d48f;&#x1d490;&#x1d491;&#x1d492;&#x1d493;
+ &#x1d494;&#x1d495;&#x1d496;&#x1d497;&#x1d498;&#x1d499;
+ &#x1d49a;&#x1d49b;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d49c;&#x212c;&#x1d49e;&#x1d49f;&#x2130;&#x2131;
+ &#x1d4a2;&#x210b;&#x2110;&#x1d4a5;&#x1d4a6;&#x2112;
+ &#x2133;&#x1d4a9;&#x1d4aa;&#x1d4ab;&#x1d4ac;&#x211b;
+ &#x1d4ae;&#x1d4af;&#x1d4b0;&#x1d4b1;&#x1d4b2;&#x1d4b3;
+ &#x1d4b4;&#x1d4b5;
+ &#x1d4b6;&#x1d4b7;&#x1d4b8;&#x1d4b9;&#x212f;&#x1d4bb;
+ &#x210a;&#x1d4bd;&#x1d4be;&#x1d4bf;&#x1d4c0;&#x1d4c1;
+ &#x1d4c2;&#x1d4c3;&#x2134;&#x1d4c5;&#x1d4c6;&#x1d4c7;
+ &#x1d4c8;&#x1d4c9;&#x1d4ca;&#x1d4cb;&#x1d4cc;&#x1d4cd;
+ &#x1d4ce;&#x1d4cf;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d4d0;&#x1d4d1;&#x1d4d2;&#x1d4d3;&#x1d4d4;&#x1d4d5;
+ &#x1d4d6;&#x1d4d7;&#x1d4d8;&#x1d4d9;&#x1d4da;&#x1d4db;
+ &#x1d4dc;&#x1d4dd;&#x1d4de;&#x1d4df;&#x1d4e0;&#x1d4e1;
+ &#x1d4e2;&#x1d4e3;&#x1d4e4;&#x1d4e5;&#x1d4e6;&#x1d4e7;
+ &#x1d4e8;&#x1d4e9;
+ &#x1d4ea;&#x1d4eb;&#x1d4ec;&#x1d4ed;&#x1d4ee;&#x1d4ef;
+ &#x1d4f0;&#x1d4f1;&#x1d4f2;&#x1d4f3;&#x1d4f4;&#x1d4f5;
+ &#x1d4f6;&#x1d4f7;&#x1d4f8;&#x1d4f9;&#x1d4fa;&#x1d4fb;
+ &#x1d4fc;&#x1d4fd;&#x1d4fe;&#x1d4ff;&#x1d500;&#x1d501;
+ &#x1d502;&#x1d503;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d504;&#x1d505;&#x212d;&#x1d507;&#x1d508;&#x1d509;
+ &#x1d50a;&#x210c;&#x2111;&#x1d50d;&#x1d50e;&#x1d50f;
+ &#x1d510;&#x1d511;&#x1d512;&#x1d513;&#x1d514;&#x211c;
+ &#x1d516;&#x1d517;&#x1d518;&#x1d519;&#x1d51a;&#x1d51b;
+ &#x1d51c;&#x2128;
+ &#x1d51e;&#x1d51f;&#x1d520;&#x1d521;&#x1d522;&#x1d523;
+ &#x1d524;&#x1d525;&#x1d526;&#x1d527;&#x1d528;&#x1d529;
+ &#x1d52a;&#x1d52b;&#x1d52c;&#x1d52d;&#x1d52e;&#x1d52f;
+ &#x1d530;&#x1d531;&#x1d532;&#x1d533;&#x1d534;&#x1d535;
+ &#x1d536;&#x1d537;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d538;&#x1d539;&#x2102;&#x1d53b;&#x1d53c;&#x1d53d;
+ &#x1d53e;&#x210d;&#x1d540;&#x1d541;&#x1d542;&#x1d543;
+ &#x1d544;&#x2115;&#x1d546;&#x2119;&#x211a;&#x211d;
+ &#x1d54a;&#x1d54b;&#x1d54c;&#x1d54d;&#x1d54e;&#x1d54f;
+ &#x1d550;&#x2124;
+ &#x1d552;&#x1d553;&#x1d554;&#x1d555;&#x1d556;&#x1d557;
+ &#x1d558;&#x1d559;&#x1d55a;&#x1d55b;&#x1d55c;&#x1d55d;
+ &#x1d55e;&#x1d55f;&#x1d560;&#x1d561;&#x1d562;&#x1d563;
+ &#x1d564;&#x1d565;&#x1d566;&#x1d567;&#x1d568;&#x1d569;
+ &#x1d56a;&#x1d56b;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d56c;&#x1d56d;&#x1d56e;&#x1d56f;&#x1d570;&#x1d571;
+ &#x1d572;&#x1d573;&#x1d574;&#x1d575;&#x1d576;&#x1d577;
+ &#x1d578;&#x1d579;&#x1d57a;&#x1d57b;&#x1d57c;&#x1d57d;
+ &#x1d57e;&#x1d57f;&#x1d580;&#x1d581;&#x1d582;&#x1d583;
+ &#x1d584;&#x1d585;
+ &#x1d586;&#x1d587;&#x1d588;&#x1d589;&#x1d58a;&#x1d58b;
+ &#x1d58c;&#x1d58d;&#x1d58e;&#x1d58f;&#x1d590;&#x1d591;
+ &#x1d592;&#x1d593;&#x1d594;&#x1d595;&#x1d596;&#x1d597;
+ &#x1d598;&#x1d599;&#x1d59a;&#x1d59b;&#x1d59c;&#x1d59d;
+ &#x1d59e;&#x1d59f;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d5a0;&#x1d5a1;&#x1d5a2;&#x1d5a3;&#x1d5a4;&#x1d5a5;
+ &#x1d5a6;&#x1d5a7;&#x1d5a8;&#x1d5a9;&#x1d5aa;&#x1d5ab;
+ &#x1d5ac;&#x1d5ad;&#x1d5ae;&#x1d5af;&#x1d5b0;&#x1d5b1;
+ &#x1d5b2;&#x1d5b3;&#x1d5b4;&#x1d5b5;&#x1d5b6;&#x1d5b7;
+ &#x1d5b8;&#x1d5b9;
+ &#x1d5ba;&#x1d5bb;&#x1d5bc;&#x1d5bd;&#x1d5be;&#x1d5bf;
+ &#x1d5c0;&#x1d5c1;&#x1d5c2;&#x1d5c3;&#x1d5c4;&#x1d5c5;
+ &#x1d5c6;&#x1d5c7;&#x1d5c8;&#x1d5c9;&#x1d5ca;&#x1d5cb;
+ &#x1d5cc;&#x1d5cd;&#x1d5ce;&#x1d5cf;&#x1d5d0;&#x1d5d1;
+ &#x1d5d2;&#x1d5d3;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d5d4;&#x1d5d5;&#x1d5d6;&#x1d5d7;&#x1d5d8;&#x1d5d9;
+ &#x1d5da;&#x1d5db;&#x1d5dc;&#x1d5dd;&#x1d5de;&#x1d5df;
+ &#x1d5e0;&#x1d5e1;&#x1d5e2;&#x1d5e3;&#x1d5e4;&#x1d5e5;
+ &#x1d5e6;&#x1d5e7;&#x1d5e8;&#x1d5e9;&#x1d5ea;&#x1d5eb;
+ &#x1d5ec;&#x1d5ed;
+ &#x1d5ee;&#x1d5ef;&#x1d5f0;&#x1d5f1;&#x1d5f2;&#x1d5f3;
+ &#x1d5f4;&#x1d5f5;&#x1d5f6;&#x1d5f7;&#x1d5f8;&#x1d5f9;
+ &#x1d5fa;&#x1d5fb;&#x1d5fc;&#x1d5fd;&#x1d5fe;&#x1d5ff;
+ &#x1d600;&#x1d601;&#x1d602;&#x1d603;&#x1d604;&#x1d605;
+ &#x1d606;&#x1d607;</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d608;&#x1d609;&#x1d60a;&#x1d60b;&#x1d60c;&#x1d60d;
+ &#x1d60e;&#x1d60f;&#x1d610;&#x1d611;&#x1d612;&#x1d613;
+ &#x1d614;&#x1d615;&#x1d616;&#x1d617;&#x1d618;&#x1d619;
+ &#x1d61a;&#x1d61b;&#x1d61c;&#x1d61d;&#x1d61e;&#x1d61f;
+ &#x1d620;&#x1d621;
+ &#x1d622;&#x1d623;&#x1d624;&#x1d625;&#x1d626;&#x1d627;
+ &#x1d628;&#x1d629;&#x1d62a;&#x1d62b;&#x1d62c;&#x1d62d;
+ &#x1d62e;&#x1d62f;&#x1d630;&#x1d631;&#x1d632;&#x1d633;
+ &#x1d634;&#x1d635;&#x1d636;&#x1d637;&#x1d638;&#x1d639;
+ &#x1d63a;&#x1d63b;</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d63c;&#x1d63d;&#x1d63e;&#x1d63f;&#x1d640;&#x1d641;
+ &#x1d642;&#x1d643;&#x1d644;&#x1d645;&#x1d646;&#x1d647;
+ &#x1d648;&#x1d649;&#x1d64a;&#x1d64b;&#x1d64c;&#x1d64d;
+ &#x1d64e;&#x1d64f;&#x1d650;&#x1d651;&#x1d652;&#x1d653;
+ &#x1d654;&#x1d655;
+ &#x1d656;&#x1d657;&#x1d658;&#x1d659;&#x1d65a;&#x1d65b;
+ &#x1d65c;&#x1d65d;&#x1d65e;&#x1d65f;&#x1d660;&#x1d661;
+ &#x1d662;&#x1d663;&#x1d664;&#x1d665;&#x1d666;&#x1d667;
+ &#x1d668;&#x1d669;&#x1d66a;&#x1d66b;&#x1d66c;&#x1d66d;
+ &#x1d66e;&#x1d66f;</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d670;&#x1d671;&#x1d672;&#x1d673;&#x1d674;&#x1d675;
+ &#x1d676;&#x1d677;&#x1d678;&#x1d679;&#x1d67a;&#x1d67b;
+ &#x1d67c;&#x1d67d;&#x1d67e;&#x1d67f;&#x1d680;&#x1d681;
+ &#x1d682;&#x1d683;&#x1d684;&#x1d685;&#x1d686;&#x1d687;
+ &#x1d688;&#x1d689;
+ &#x1d68a;&#x1d68b;&#x1d68c;&#x1d68d;&#x1d68e;&#x1d68f;
+ &#x1d690;&#x1d691;&#x1d692;&#x1d693;&#x1d694;&#x1d695;
+ &#x1d696;&#x1d697;&#x1d698;&#x1d699;&#x1d69a;&#x1d69b;
+ &#x1d69c;&#x1d69d;&#x1d69e;&#x1d69f;&#x1d6a0;&#x1d6a1;
+ &#x1d6a2;&#x1d6a3;</mtext>
+ </mrow>
+ </math>
+ <br>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a.html
new file mode 100644
index 0000000000..0cd85b9bc9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1a.html
@@ -0,0 +1,225 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ <link rel="match" href="mathvariant-1a-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext mathvariant="normal">ABCDEFGHIJKLMNOPQRSTUVWXYZ</mtext>
+ <mtext mathvariant="normal">abcdefghijklmnopqrstuvwxyz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="italic">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-italic">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="script">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-script">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="fraktur">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="double-struck">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-fraktur">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-sans-serif">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif-italic">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif-bold-italic">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="monospace">
+ ABCDEF
+ GHIJKL
+ MNOPQR
+ STUVWX
+ YZ
+ abcdef
+ ghijkl
+ mnopqr
+ stuvwx
+ yz</mtext>
+ </mrow>
+ </math>
+ <br>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b-ref.html
new file mode 100644
index 0000000000..6dd2f622a0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b-ref.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext>0123456789</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d7ce;&#x1d7cf;&#x1d7d0;&#x1d7d1;&#x1d7d2;&#x1d7d3;
+ &#x1d7d4;&#x1d7d5;&#x1d7d6;&#x1d7d7;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>0123456789</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>0123456789</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d7d8;&#x1d7d9;&#x1d7da;&#x1d7db;&#x1d7dc;&#x1d7dd;
+ &#x1d7de;&#x1d7df;&#x1d7e0;&#x1d7e1;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d7e2;&#x1d7e3;&#x1d7e4;&#x1d7e5;&#x1d7e6;&#x1d7e7;
+ &#x1d7e8;&#x1d7e9;&#x1d7ea;&#x1d7eb;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d7ec;&#x1d7ed;&#x1d7ee;&#x1d7ef;&#x1d7f0;&#x1d7f1;
+ &#x1d7f2;&#x1d7f3;&#x1d7f4;&#x1d7f5;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d7f6;&#x1d7f7;&#x1d7f8;&#x1d7f9;&#x1d7fa;&#x1d7fb;
+ &#x1d7fc;&#x1d7fd;&#x1d7fe;&#x1d7ff;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b.html
new file mode 100644
index 0000000000..c744469e42
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1b.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ <link rel="match" href="mathvariant-1b-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext mathvariant="normal">0123456789</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="italic">0123456789</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-italic">0123456789</mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="script">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-script">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="fraktur">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="double-struck">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-fraktur">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-sans-serif">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif-italic">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif-bold-italic">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="monospace">
+ 012345
+ 6789
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c-ref.html
new file mode 100644
index 0000000000..6a4f8cf1c4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c-ref.html
@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d6a8;&#x1d6a9;&#x1d6aa;&#x1d6ab;&#x1d6ac;&#x1d6ad;
+ &#x1d6ae;&#x1d6af;&#x1d6b0;&#x1d6b1;&#x1d6b2;&#x1d6b3;
+ &#x1d6b4;&#x1d6b5;&#x1d6b6;&#x1d6b7;&#x1d6b8;&#x1d6b9;
+ &#x1d6ba;&#x1d6bb;&#x1d6bc;&#x1d6bd;&#x1d6be;&#x1d6bf;
+ &#x1d6c0;&#x1d6c1;&#x1d6c2;&#x1d6c3;&#x1d6c4;&#x1d6c5;
+ &#x1d6c6;&#x1d6c7;&#x1d6c8;&#x1d6c9;&#x1d6ca;&#x1d6cb;
+ &#x1d6cc;&#x1d6cd;&#x1d6ce;&#x1d6cf;&#x1d6d0;&#x1d6d1;
+ &#x1d6d2;&#x1d6d3;&#x1d6d4;&#x1d6d5;&#x1d6d6;&#x1d6d7;
+ &#x1d6d8;&#x1d6d9;&#x1d6da;&#x1d6db;&#x1d6dc;&#x1d6dd;
+ &#x1d6de;&#x1d6df;&#x1d6e0;&#x1d6e1;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d6e2;&#x1d6e3;&#x1d6e4;&#x1d6e5;&#x1d6e6;&#x1d6e7;
+ &#x1d6e8;&#x1d6e9;&#x1d6ea;&#x1d6eb;&#x1d6ec;&#x1d6ed;
+ &#x1d6ee;&#x1d6ef;&#x1d6f0;&#x1d6f1;&#x1d6f2;&#x1d6f3;
+ &#x1d6f4;&#x1d6f5;&#x1d6f6;&#x1d6f7;&#x1d6f8;&#x1d6f9;
+ &#x1d6fa;&#x1d6fb;&#x1d6fc;&#x1d6fd;&#x1d6fe;&#x1d6ff;
+ &#x1d700;&#x1d701;&#x1d702;&#x1d703;&#x1d704;&#x1d705;
+ &#x1d706;&#x1d707;&#x1d708;&#x1d709;&#x1d70a;&#x1d70b;
+ &#x1d70c;&#x1d70d;&#x1d70e;&#x1d70f;&#x1d710;&#x1d711;
+ &#x1d712;&#x1d713;&#x1d714;&#x1d715;&#x1d716;&#x1d717;
+ &#x1d718;&#x1d719;&#x1d71a;&#x1d71b;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d71c;&#x1d71d;&#x1d71e;&#x1d71f;&#x1d720;&#x1d721;
+ &#x1d722;&#x1d723;&#x1d724;&#x1d725;&#x1d726;&#x1d727;
+ &#x1d728;&#x1d729;&#x1d72a;&#x1d72b;&#x1d72c;&#x1d72d;
+ &#x1d72e;&#x1d72f;&#x1d730;&#x1d731;&#x1d732;&#x1d733;
+ &#x1d734;&#x1d735;&#x1d736;&#x1d737;&#x1d738;&#x1d739;
+ &#x1d73a;&#x1d73b;&#x1d73c;&#x1d73d;&#x1d73e;&#x1d73f;
+ &#x1d740;&#x1d741;&#x1d742;&#x1d743;&#x1d744;&#x1d745;
+ &#x1d746;&#x1d747;&#x1d748;&#x1d749;&#x1d74a;&#x1d74b;
+ &#x1d74c;&#x1d74d;&#x1d74e;&#x1d74f;&#x1d750;&#x1d751;
+ &#x1d752;&#x1d753;&#x1d754;&#x1d755;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d756;&#x1d757;&#x1d758;&#x1d759;&#x1d75a;&#x1d75b;
+ &#x1d75c;&#x1d75d;&#x1d75e;&#x1d75f;&#x1d760;&#x1d761;
+ &#x1d762;&#x1d763;&#x1d764;&#x1d765;&#x1d766;&#x1d767;
+ &#x1d768;&#x1d769;&#x1d76a;&#x1d76b;&#x1d76c;&#x1d76d;
+ &#x1d76e;&#x1d76f;&#x1d770;&#x1d771;&#x1d772;&#x1d773;
+ &#x1d774;&#x1d775;&#x1d776;&#x1d777;&#x1d778;&#x1d779;
+ &#x1d77a;&#x1d77b;&#x1d77c;&#x1d77d;&#x1d77e;&#x1d77f;
+ &#x1d780;&#x1d781;&#x1d782;&#x1d783;&#x1d784;&#x1d785;
+ &#x1d786;&#x1d787;&#x1d788;&#x1d789;&#x1d78a;&#x1d78b;
+ &#x1d78c;&#x1d78d;&#x1d78e;&#x1d78f;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1d790;&#x1d791;&#x1d792;&#x1d793;&#x1d794;&#x1d795;
+ &#x1d796;&#x1d797;&#x1d798;&#x1d799;&#x1d79a;&#x1d79b;
+ &#x1d79c;&#x1d79d;&#x1d79e;&#x1d79f;&#x1d7a0;&#x1d7a1;
+ &#x1d7a2;&#x1d7a3;&#x1d7a4;&#x1d7a5;&#x1d7a6;&#x1d7a7;
+ &#x1d7a8;&#x1d7a9;&#x1d7aa;&#x1d7ab;&#x1d7ac;&#x1d7ad;
+ &#x1d7ae;&#x1d7af;&#x1d7b0;&#x1d7b1;&#x1d7b2;&#x1d7b3;
+ &#x1d7b4;&#x1d7b5;&#x1d7b6;&#x1d7b7;&#x1d7b8;&#x1d7b9;
+ &#x1d7ba;&#x1d7bb;&#x1d7bc;&#x1d7bd;&#x1d7be;&#x1d7bf;
+ &#x1d7c0;&#x1d7c1;&#x1d7c2;&#x1d7c3;&#x1d7c4;&#x1d7c5;
+ &#x1d7c6;&#x1d7c7;&#x1d7c8;&#x1d7c9;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c.html
new file mode 100644
index 0000000000..9e99d9ba64
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1c.html
@@ -0,0 +1,248 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ <link rel="match" href="mathvariant-1c-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext mathvariant="normal">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="italic">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-italic">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="script">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-script">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="fraktur">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="double-struck">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-fraktur">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="bold-sans-serif">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif-italic">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="sans-serif-bold-italic">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="monospace">
+ &#x0391;&#x0392;&#x0393;&#x0394;&#x0395;&#x0396;
+ &#x0397;&#x0398;&#x0399;&#x039a;&#x039b;&#x039c;
+ &#x039d;&#x039e;&#x039f;&#x03a0;&#x03a1;&#x03f4;
+ &#x03a3;&#x03a4;&#x03a5;&#x03a6;&#x03a7;&#x03a8;
+ &#x03a9;&#x2207;&#x03b1;&#x03b2;&#x03b3;&#x03b4;
+ &#x03b5;&#x03b6;&#x03b7;&#x03b8;&#x03b9;&#x03ba;
+ &#x03bb;&#x03bc;&#x03bd;&#x03be;&#x03bf;&#x03c0;
+ &#x03c1;&#x03c2;&#x03c3;&#x03c4;&#x03c5;&#x03c6;
+ &#x03c7;&#x03c8;&#x03c9;&#x2202;&#x03f5;&#x03d1;
+ &#x03f0;&#x03d5;&#x03f1;&#x03d6;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d-ref.html
new file mode 100644
index 0000000000..6a7226e255
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d-ref.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1EEA1;&#x1EEA2;&#x1EEA3;&#x1EEA5;&#x1EEA6;&#x1EEA7;
+ &#x1EEA8;&#x1EEA9;&#x1EEAB;&#x1EEAC;&#x1EEAD;&#x1EEAE;
+ &#x1EEAF;&#x1EEB0;&#x1EEB1;&#x1EEB2;&#x1EEB3;&#x1EEB4;
+ &#x1EEB5;&#x1EEB6;&#x1EEB7;&#x1EEB8;&#x1EEB9;&#x1EEBA;
+ &#x1EEBB
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1EE21;&#x1EE22;&#x1EE24;&#x1EE27;&#x1EE29;&#x1EE2A;
+ &#x1EE2B;&#x1EE2C;&#x1EE2D;&#x1EE2E;&#x1EE2F;&#x1EE30;
+ &#x1EE31;&#x1EE32;&#x1EE34;&#x1EE35;&#x1EE36;&#x1EE37;
+ &#x1EE39;&#x1EE3B;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1EE42;&#x1EE47;&#x1EE49;&#x1EE4B;&#x1EE4D;&#x1EE4E;
+ &#x1EE4F;&#x1EE51;&#x1EE52;&#x1EE54;&#x1EE57;&#x1EE59
+ &#x1EE5B;&#x1EE5D;&#x1EE5F;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1EE80;&#x1EE81;&#x1EE82;&#x1EE83;&#x1EE84;&#x1EE85;
+ &#x1EE86;&#x1EE87;&#x1EE88;&#x1EE89;&#x1EE8B;&#x1EE8C;
+ &#x1EE8D;&#x1EE8E;&#x1EE8F;&#x1EE90;&#x1EE91;&#x1EE92;
+ &#x1EE93;&#x1EE94;&#x1EE95;&#x1EE96;&#x1EE97;&#x1EE98;
+ &#x1EE99;&#x1EE9A;&#x1EE9B;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext>
+ &#x1EE61;&#x1EE62;&#x1EE64;&#x1EE67;&#x1EE68;&#x1EE69;
+ &#x1EE6A;&#x1EE6C;&#x1EE6D;&#x1EE6E;&#x1EE6F;&#x1EE70;
+ &#x1EE71;&#x1EE72;&#x1EE74;&#x1EE75;&#x1EE76;&#x1EE77;
+ &#x1EE79;&#x1EE7A;&#x1EE7B;&#x1EE7C;&#x1EE7E;
+ </mtext>
+ </mrow>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d.html
new file mode 100644
index 0000000000..5d1de1e87b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-1d.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test mathvariant character mappings</title>
+ <link rel="match" href="mathvariant-1d-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext mathvariant="double-struck">
+ &#x0628;&#x062C;&#x062F;&#x0648;&#x0632;&#x062D;
+ &#x0637;&#x064A;&#x0644;&#x0645;&#x0646;&#x0633;
+ &#x0639;&#x0641;&#x0635;&#x0642;&#x0631;&#x0634;
+ &#x062A;&#x062B;&#x062E;&#x0630;&#x0636;&#x0638;
+ &#x063A;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="initial">
+ &#x0628;&#x062C;&#x0647;&#x062D;&#x064A;&#x0643;
+ &#x0644;&#x0645;&#x0646;&#x0633;&#x0639;&#x0641;
+ &#x0635;&#x0642;&#x0634;&#x062A;&#x062B;&#x062E;
+ &#x0636;&#x063A;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="tailed">
+ &#x062C;&#x062D;&#x064A;&#x0644;&#x0646;&#x0633;
+ &#x0639;&#x0635;&#x0642;&#x0634;&#x062E;&#x0636;
+ &#x063A;&#x06BA;&#x066F;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="looped">
+ &#x0627;&#x0628;&#x062C;&#x062F;&#x0647;&#x0648;
+ &#x0632;&#x062D;&#x0637;&#x064A;&#x0644;&#x0645;
+ &#x0646;&#x0633;&#x0639;&#x0641;&#x0635;&#x0642;
+ &#x0631;&#x0634;&#x062A;&#x062B;&#x062E;&#x0630;
+ &#x0636;&#x0638;&#x063A;
+ </mtext>
+ </mrow>
+ </math>
+ <br>
+ <math>
+ <mrow>
+ <mtext mathvariant="stretched">
+ &#x0628;&#x062C;&#x0647;&#x062D;&#x0637;&#x064A;
+ &#x0643;&#x0645;&#x0646;&#x0633;&#x0639;&#x0641;
+ &#x0635;&#x0642;&#x0634;&#x062A;&#x062B;&#x062E;
+ &#x0636;&#x0638;&#x063A;&#x066E;&#x06A1;
+ </mtext>
+ </mrow>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2-ref.html
new file mode 100644
index 0000000000..6118ad058a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mathvariant exception mappings</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext>&#x1d6a4;&#x1d6a5;</mtext>
+ <mtext>&#x0131;&#x0237;</mtext>
+ <mtext>&#x1d7ca;&#x1d7cb;</mtext>
+ <mtext>&#x03DC;&#x03DD;</mtext>
+ </mrow>
+ </math>
+ <p>
+ <math>
+ <mrow>
+ <mi mathvariant="italic">&imath;</mi>
+ <mi mathvariant="italic">&jmath;</mi>
+ <mi mathvariant="normal">&imath;&imath;</mi>
+ <mi mathvariant="normal">&jmath;&jmath;</mi>
+ </mrow>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2.html
new file mode 100644
index 0000000000..3c2cc7b45a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mathvariant exception mappings</title>
+ <link rel="match" href="mathvariant-2-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext mathvariant="italic">&#x0131;&#x0237;</mtext>
+ <mtext mathvariant="bold">&#x0131;&#x0237;</mtext>
+ <mtext mathvariant="bold">&#x03DC;&#x03DD;</mtext>
+ <mtext mathvariant="italic">&#x03DC;&#x03DD;</mtext>
+ </mrow>
+ </math>
+ <p>
+ <math>
+ <mrow>
+ <mi>&imath;</mi>
+ <mi>&jmath;</mi>
+ <mi>&imath;&imath;</mi>
+ <mi>&jmath;&jmath;</mi>
+ </mrow>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4-ref.html
new file mode 100644
index 0000000000..9c51d61b67
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4-ref.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>More mathvariant tests</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <!-- mathvariant on characters that are already in the Mathematical
+ AlphanumericSymbols or are exceptions (should not have any
+ effect).-->
+ <mtext>&#x1d49c;</mtext>
+ <mtext>&#x212c;</mtext>
+ <!-- mathvariant on characters for which there is no equivalent mathvariant
+ form in Unicode (should not have any effect) -->
+ <mtext>&#x00e1;</mtext>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mrow>
+ <!-- mathvariant on multi-char token elements (should apply to all the
+ characters) -->
+ <mtext>&#x1d670;&#x1d670;&#x1d670;</mtext>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mrow>
+ <!-- mathvariant on mstyle (should apply to all token element descendants
+ like single-char mi, mtext etc) -->
+ <mtext mathvariant="sans-serif">cos</mtext>
+ <mo>&#x2061;</mo>
+ <mi mathvariant="sans-serif">x</mi>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mrow>
+ <!-- mathvariant on math (should apply to all token element descendants
+ like single-char mi, mtext etc) -->
+ <mtext mathvariant="sans-serif">cos</mtext>
+ <mo>&#x2061;</mo>
+ <mi mathvariant="sans-serif">x</mi>
+ </mrow>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4.html
new file mode 100644
index 0000000000..ca66e87f4a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-4.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>More mathvariant tests</title>
+ <link rel="match" href="mathvariant-4-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mtext mathvariant="fraktur">&#x1d49c;</mtext>
+ <mtext mathvariant="fraktur">&#x212c;</mtext>
+ <mtext mathvariant="fraktur">&#x00e1;</mtext>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mrow>
+ <mtext mathvariant="monospace">AAA</mtext>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mstyle mathvariant="sans-serif">
+ <mrow>
+ <mtext>cos</mtext>
+ <mo>&#x2061;</mo>
+ <mi>x</mi>
+ </mrow>
+ </mstyle>
+ </math>
+
+ <p>
+
+ <math mathvariant="sans-serif">
+ <mrow>
+ <mtext>cos</mtext>
+ <mo>&#x2061;</mo>
+ <mi>x</mi>
+ </mrow>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5-ref.html
new file mode 100644
index 0000000000..1854ce4b13
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5-ref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dynamic mathvariant tests</title>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mi id="Mi0" mathvariant="script">A</mi>
+ <mi id="Mi1">BB</mi>
+ <mi id="Mi2">B</mi>
+ <mi id="Mi3">A</mi>
+ <mi id="Mi4" mathvariant="script">A</mi>
+ <mtext id="Mtext0">A</mtext>
+ <mtext id="Mtext2" mathvariant="script">A</mtext>
+ <mtext id="Mtext4" mathvariant="script">A</mtext>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mrow>
+ <mstyle id="Mstyle0" mathvariant="fraktur">
+ <mtext>Hello</mtext>
+ </mstyle>
+ <mstyle id="Mstyle1" mathvariant="monospace">
+ <mtext>Hello</mtext>
+ </mstyle>
+ <mstyle id="Mstyle2">
+ <mtext>Hello</mtext>
+ </mstyle>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math id="Math0" mathvariant="fraktur">
+ <mtext>Hello</mtext>
+ </math>
+ <math id="Math1" mathvariant="monospace">
+ <mtext>Hello</mtext>
+ </math>
+ <math id="Math2">
+ <mtext>Hello</mtext>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5.html
new file mode 100644
index 0000000000..f135fbe7a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-5.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Dynamic mathvariant tests</title>
+ <link rel="match" href="mathvariant-5-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mrow>
+ <mi id="Mi0">A</mi>
+ <mi id="Mi1">A</mi>
+ <mi id="Mi2">AA</mi>
+ <mi id="Mi3" mathvariant="fraktur">A</mi>
+ <mi id="Mi4" mathvariant="monospace">A</mi>
+ <mtext id="Mtext0" mathvariant="monospace">A</mtext>
+ <mtext id="Mtext2" mathvariant="monospace">A</mtext>
+ <mtext id="Mtext4">A</mtext>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math>
+ <mrow>
+ <mstyle id="Mstyle0">
+ <mtext>Hello</mtext>
+ </mstyle>
+ <mstyle id="Mstyle1" mathvariant="bold-fraktur">
+ <mtext>Hello</mtext>
+ </mstyle>
+ <mstyle id="Mstyle2" mathvariant="bold">
+ <mtext>Hello</mtext>
+ </mstyle>
+ </mrow>
+ </math>
+
+ <p>
+
+ <math id="Math0">
+ <mtext>Hello</mtext>
+ </math>
+ <math id="Math1" mathvariant="fraktur">
+ <mtext>Hello</mtext>
+ </math>
+ <math id="Math2" mathvariant="bold">
+ <mtext>Hello</mtext>
+ </math>
+ <script>
+ function doTest()
+ {
+ document.getElementById("Mi0").setAttribute("mathvariant", "script");
+ document.getElementById("Mi1").innerHTML = "BB";
+ document.getElementById("Mi2").innerHTML = "B";
+ document.getElementById("Mi3").removeAttribute("mathvariant");
+ document.getElementById("Mi4").setAttribute("mathvariant", "script");
+ document.getElementById("Mtext0").removeAttribute("mathvariant");
+ document.getElementById("Mtext2").setAttribute("mathvariant", "script");
+ document.getElementById("Mtext4").setAttribute("mathvariant", "script");
+ document.getElementById("Mstyle0").setAttribute("mathvariant", "fraktur");
+ document.getElementById("Mstyle1").setAttribute("mathvariant", "monospace");
+ document.getElementById("Mstyle2").removeAttribute("mathvariant");
+ document.getElementById("Math0").setAttribute("mathvariant", "fraktur");
+ document.getElementById("Math1").setAttribute("mathvariant", "monospace");
+ document.getElementById("Math2").removeAttribute("mathvariant");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font-ref.html
new file mode 100644
index 0000000000..6ebfd75c2f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Basic mathvariant transforms with the default font (reference)</title>
+ </head>
+ <body>
+ <p>Test passes if you see three lines of text rendered with corresponding
+ italic, bold, bold-italic characters from the
+ Mathematical Alphanumeric Symbols block:</p>
+ <p><math><mtext>𝐼𝑡𝑎𝑙𝑖𝑐</mtext></math></p>
+ <p><math><mtext>𝐁𝐨𝐥𝐝</mtext></math></p>
+ <p><math><mtext>𝑩𝒐𝒍𝒅𝑰𝒕𝒂𝒍𝒊𝒄</mtext></math></p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font.html
new file mode 100644
index 0000000000..24c868c495
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-basic-transforms-with-default-font.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Basic mathvariant transforms with the default font</title>
+ <link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+ <link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1789083">
+ <link rel="match" href="mathvariant-basic-transforms-with-default-font-ref.html"/>
+ <meta name="assert" content="Verify that the default font provides Mathematical Alphanumeric Symbols to perform basic mathvariant transforms (italic, bold, bold-italic), without requiring some kind of style fallback.">
+ </head>
+ <body>
+ <p>Test passes if you see three lines of text rendered with corresponding
+ italic, bold, bold-italic characters from the
+ Mathematical Alphanumeric Symbols block:</p>
+ <p><math><mtext mathvariant="italic">Italic</mtext></math></p>
+ <p><math><mtext mathvariant="bold">Bold</mtext></math></p>
+ <p><math><mtext mathvariant="bold-italic">BoldItalic</mtext></math></p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur-ref.html
new file mode 100644
index 0000000000..44588948e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur-ref.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-fraktur (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-fraktur.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D56C;</mtext></math>=<span>1D56C</span></span>
+ <span><math class="testfont"><mtext>&#x1D56D;</mtext></math>=<span>1D56D</span></span>
+ <span><math class="testfont"><mtext>&#x1D56E;</mtext></math>=<span>1D56E</span></span>
+ <span><math class="testfont"><mtext>&#x1D56F;</mtext></math>=<span>1D56F</span></span>
+ <span><math class="testfont"><mtext>&#x1D570;</mtext></math>=<span>1D570</span></span>
+ <span><math class="testfont"><mtext>&#x1D571;</mtext></math>=<span>1D571</span></span>
+ <span><math class="testfont"><mtext>&#x1D572;</mtext></math>=<span>1D572</span></span>
+ <span><math class="testfont"><mtext>&#x1D573;</mtext></math>=<span>1D573</span></span>
+ <span><math class="testfont"><mtext>&#x1D574;</mtext></math>=<span>1D574</span></span>
+ <span><math class="testfont"><mtext>&#x1D575;</mtext></math>=<span>1D575</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D576;</mtext></math>=<span>1D576</span></span>
+ <span><math class="testfont"><mtext>&#x1D577;</mtext></math>=<span>1D577</span></span>
+ <span><math class="testfont"><mtext>&#x1D578;</mtext></math>=<span>1D578</span></span>
+ <span><math class="testfont"><mtext>&#x1D579;</mtext></math>=<span>1D579</span></span>
+ <span><math class="testfont"><mtext>&#x1D57A;</mtext></math>=<span>1D57A</span></span>
+ <span><math class="testfont"><mtext>&#x1D57B;</mtext></math>=<span>1D57B</span></span>
+ <span><math class="testfont"><mtext>&#x1D57C;</mtext></math>=<span>1D57C</span></span>
+ <span><math class="testfont"><mtext>&#x1D57D;</mtext></math>=<span>1D57D</span></span>
+ <span><math class="testfont"><mtext>&#x1D57E;</mtext></math>=<span>1D57E</span></span>
+ <span><math class="testfont"><mtext>&#x1D57F;</mtext></math>=<span>1D57F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D580;</mtext></math>=<span>1D580</span></span>
+ <span><math class="testfont"><mtext>&#x1D581;</mtext></math>=<span>1D581</span></span>
+ <span><math class="testfont"><mtext>&#x1D582;</mtext></math>=<span>1D582</span></span>
+ <span><math class="testfont"><mtext>&#x1D583;</mtext></math>=<span>1D583</span></span>
+ <span><math class="testfont"><mtext>&#x1D584;</mtext></math>=<span>1D584</span></span>
+ <span><math class="testfont"><mtext>&#x1D585;</mtext></math>=<span>1D585</span></span>
+ <span><math class="testfont"><mtext>&#x1D586;</mtext></math>=<span>1D586</span></span>
+ <span><math class="testfont"><mtext>&#x1D587;</mtext></math>=<span>1D587</span></span>
+ <span><math class="testfont"><mtext>&#x1D588;</mtext></math>=<span>1D588</span></span>
+ <span><math class="testfont"><mtext>&#x1D589;</mtext></math>=<span>1D589</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D58A;</mtext></math>=<span>1D58A</span></span>
+ <span><math class="testfont"><mtext>&#x1D58B;</mtext></math>=<span>1D58B</span></span>
+ <span><math class="testfont"><mtext>&#x1D58C;</mtext></math>=<span>1D58C</span></span>
+ <span><math class="testfont"><mtext>&#x1D58D;</mtext></math>=<span>1D58D</span></span>
+ <span><math class="testfont"><mtext>&#x1D58E;</mtext></math>=<span>1D58E</span></span>
+ <span><math class="testfont"><mtext>&#x1D58F;</mtext></math>=<span>1D58F</span></span>
+ <span><math class="testfont"><mtext>&#x1D590;</mtext></math>=<span>1D590</span></span>
+ <span><math class="testfont"><mtext>&#x1D591;</mtext></math>=<span>1D591</span></span>
+ <span><math class="testfont"><mtext>&#x1D592;</mtext></math>=<span>1D592</span></span>
+ <span><math class="testfont"><mtext>&#x1D593;</mtext></math>=<span>1D593</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D594;</mtext></math>=<span>1D594</span></span>
+ <span><math class="testfont"><mtext>&#x1D595;</mtext></math>=<span>1D595</span></span>
+ <span><math class="testfont"><mtext>&#x1D596;</mtext></math>=<span>1D596</span></span>
+ <span><math class="testfont"><mtext>&#x1D597;</mtext></math>=<span>1D597</span></span>
+ <span><math class="testfont"><mtext>&#x1D598;</mtext></math>=<span>1D598</span></span>
+ <span><math class="testfont"><mtext>&#x1D599;</mtext></math>=<span>1D599</span></span>
+ <span><math class="testfont"><mtext>&#x1D59A;</mtext></math>=<span>1D59A</span></span>
+ <span><math class="testfont"><mtext>&#x1D59B;</mtext></math>=<span>1D59B</span></span>
+ <span><math class="testfont"><mtext>&#x1D59C;</mtext></math>=<span>1D59C</span></span>
+ <span><math class="testfont"><mtext>&#x1D59D;</mtext></math>=<span>1D59D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D59E;</mtext></math>=<span>1D59E</span></span>
+ <span><math class="testfont"><mtext>&#x1D59F;</mtext></math>=<span>1D59F</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur.html
new file mode 100644
index 0000000000..32d037603b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-fraktur.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-fraktur</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#bold-fraktur-mappings">
+<link rel="match" href="mathvariant-bold-fraktur-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a bold-fraktur mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-fraktur.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x41;</mtext></math>=<span>1D56C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x42;</mtext></math>=<span>1D56D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x43;</mtext></math>=<span>1D56E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x44;</mtext></math>=<span>1D56F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x45;</mtext></math>=<span>1D570</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x46;</mtext></math>=<span>1D571</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x47;</mtext></math>=<span>1D572</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x48;</mtext></math>=<span>1D573</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x49;</mtext></math>=<span>1D574</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x4A;</mtext></math>=<span>1D575</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x4B;</mtext></math>=<span>1D576</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x4C;</mtext></math>=<span>1D577</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x4D;</mtext></math>=<span>1D578</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x4E;</mtext></math>=<span>1D579</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x4F;</mtext></math>=<span>1D57A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x50;</mtext></math>=<span>1D57B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x51;</mtext></math>=<span>1D57C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x52;</mtext></math>=<span>1D57D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x53;</mtext></math>=<span>1D57E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x54;</mtext></math>=<span>1D57F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x55;</mtext></math>=<span>1D580</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x56;</mtext></math>=<span>1D581</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x57;</mtext></math>=<span>1D582</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x58;</mtext></math>=<span>1D583</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x59;</mtext></math>=<span>1D584</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x5A;</mtext></math>=<span>1D585</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x61;</mtext></math>=<span>1D586</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x62;</mtext></math>=<span>1D587</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x63;</mtext></math>=<span>1D588</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x64;</mtext></math>=<span>1D589</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x65;</mtext></math>=<span>1D58A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x66;</mtext></math>=<span>1D58B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x67;</mtext></math>=<span>1D58C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x68;</mtext></math>=<span>1D58D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x69;</mtext></math>=<span>1D58E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x6A;</mtext></math>=<span>1D58F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x6B;</mtext></math>=<span>1D590</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x6C;</mtext></math>=<span>1D591</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x6D;</mtext></math>=<span>1D592</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x6E;</mtext></math>=<span>1D593</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x6F;</mtext></math>=<span>1D594</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x70;</mtext></math>=<span>1D595</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x71;</mtext></math>=<span>1D596</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x72;</mtext></math>=<span>1D597</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x73;</mtext></math>=<span>1D598</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x74;</mtext></math>=<span>1D599</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x75;</mtext></math>=<span>1D59A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x76;</mtext></math>=<span>1D59B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x77;</mtext></math>=<span>1D59C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x78;</mtext></math>=<span>1D59D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x79;</mtext></math>=<span>1D59E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-fraktur">&#x7A;</mtext></math>=<span>1D59F</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic-ref.html
new file mode 100644
index 0000000000..9a93a37e35
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic-ref.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-italic (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D468;</mtext></math>=<span>1D468</span></span>
+ <span><math class="testfont"><mtext>&#x1D469;</mtext></math>=<span>1D469</span></span>
+ <span><math class="testfont"><mtext>&#x1D46A;</mtext></math>=<span>1D46A</span></span>
+ <span><math class="testfont"><mtext>&#x1D46B;</mtext></math>=<span>1D46B</span></span>
+ <span><math class="testfont"><mtext>&#x1D46C;</mtext></math>=<span>1D46C</span></span>
+ <span><math class="testfont"><mtext>&#x1D46D;</mtext></math>=<span>1D46D</span></span>
+ <span><math class="testfont"><mtext>&#x1D46E;</mtext></math>=<span>1D46E</span></span>
+ <span><math class="testfont"><mtext>&#x1D46F;</mtext></math>=<span>1D46F</span></span>
+ <span><math class="testfont"><mtext>&#x1D470;</mtext></math>=<span>1D470</span></span>
+ <span><math class="testfont"><mtext>&#x1D471;</mtext></math>=<span>1D471</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D472;</mtext></math>=<span>1D472</span></span>
+ <span><math class="testfont"><mtext>&#x1D473;</mtext></math>=<span>1D473</span></span>
+ <span><math class="testfont"><mtext>&#x1D474;</mtext></math>=<span>1D474</span></span>
+ <span><math class="testfont"><mtext>&#x1D475;</mtext></math>=<span>1D475</span></span>
+ <span><math class="testfont"><mtext>&#x1D476;</mtext></math>=<span>1D476</span></span>
+ <span><math class="testfont"><mtext>&#x1D477;</mtext></math>=<span>1D477</span></span>
+ <span><math class="testfont"><mtext>&#x1D478;</mtext></math>=<span>1D478</span></span>
+ <span><math class="testfont"><mtext>&#x1D479;</mtext></math>=<span>1D479</span></span>
+ <span><math class="testfont"><mtext>&#x1D47A;</mtext></math>=<span>1D47A</span></span>
+ <span><math class="testfont"><mtext>&#x1D47B;</mtext></math>=<span>1D47B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D47C;</mtext></math>=<span>1D47C</span></span>
+ <span><math class="testfont"><mtext>&#x1D47D;</mtext></math>=<span>1D47D</span></span>
+ <span><math class="testfont"><mtext>&#x1D47E;</mtext></math>=<span>1D47E</span></span>
+ <span><math class="testfont"><mtext>&#x1D47F;</mtext></math>=<span>1D47F</span></span>
+ <span><math class="testfont"><mtext>&#x1D480;</mtext></math>=<span>1D480</span></span>
+ <span><math class="testfont"><mtext>&#x1D481;</mtext></math>=<span>1D481</span></span>
+ <span><math class="testfont"><mtext>&#x1D482;</mtext></math>=<span>1D482</span></span>
+ <span><math class="testfont"><mtext>&#x1D483;</mtext></math>=<span>1D483</span></span>
+ <span><math class="testfont"><mtext>&#x1D484;</mtext></math>=<span>1D484</span></span>
+ <span><math class="testfont"><mtext>&#x1D485;</mtext></math>=<span>1D485</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D486;</mtext></math>=<span>1D486</span></span>
+ <span><math class="testfont"><mtext>&#x1D487;</mtext></math>=<span>1D487</span></span>
+ <span><math class="testfont"><mtext>&#x1D488;</mtext></math>=<span>1D488</span></span>
+ <span><math class="testfont"><mtext>&#x1D489;</mtext></math>=<span>1D489</span></span>
+ <span><math class="testfont"><mtext>&#x1D48A;</mtext></math>=<span>1D48A</span></span>
+ <span><math class="testfont"><mtext>&#x1D48B;</mtext></math>=<span>1D48B</span></span>
+ <span><math class="testfont"><mtext>&#x1D48C;</mtext></math>=<span>1D48C</span></span>
+ <span><math class="testfont"><mtext>&#x1D48D;</mtext></math>=<span>1D48D</span></span>
+ <span><math class="testfont"><mtext>&#x1D48E;</mtext></math>=<span>1D48E</span></span>
+ <span><math class="testfont"><mtext>&#x1D48F;</mtext></math>=<span>1D48F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D490;</mtext></math>=<span>1D490</span></span>
+ <span><math class="testfont"><mtext>&#x1D491;</mtext></math>=<span>1D491</span></span>
+ <span><math class="testfont"><mtext>&#x1D492;</mtext></math>=<span>1D492</span></span>
+ <span><math class="testfont"><mtext>&#x1D493;</mtext></math>=<span>1D493</span></span>
+ <span><math class="testfont"><mtext>&#x1D494;</mtext></math>=<span>1D494</span></span>
+ <span><math class="testfont"><mtext>&#x1D495;</mtext></math>=<span>1D495</span></span>
+ <span><math class="testfont"><mtext>&#x1D496;</mtext></math>=<span>1D496</span></span>
+ <span><math class="testfont"><mtext>&#x1D497;</mtext></math>=<span>1D497</span></span>
+ <span><math class="testfont"><mtext>&#x1D498;</mtext></math>=<span>1D498</span></span>
+ <span><math class="testfont"><mtext>&#x1D499;</mtext></math>=<span>1D499</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D49A;</mtext></math>=<span>1D49A</span></span>
+ <span><math class="testfont"><mtext>&#x1D49B;</mtext></math>=<span>1D49B</span></span>
+ <span><math class="testfont"><mtext>&#x1D71C;</mtext></math>=<span>1D71C</span></span>
+ <span><math class="testfont"><mtext>&#x1D71D;</mtext></math>=<span>1D71D</span></span>
+ <span><math class="testfont"><mtext>&#x1D71E;</mtext></math>=<span>1D71E</span></span>
+ <span><math class="testfont"><mtext>&#x1D71F;</mtext></math>=<span>1D71F</span></span>
+ <span><math class="testfont"><mtext>&#x1D720;</mtext></math>=<span>1D720</span></span>
+ <span><math class="testfont"><mtext>&#x1D721;</mtext></math>=<span>1D721</span></span>
+ <span><math class="testfont"><mtext>&#x1D722;</mtext></math>=<span>1D722</span></span>
+ <span><math class="testfont"><mtext>&#x1D723;</mtext></math>=<span>1D723</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D724;</mtext></math>=<span>1D724</span></span>
+ <span><math class="testfont"><mtext>&#x1D725;</mtext></math>=<span>1D725</span></span>
+ <span><math class="testfont"><mtext>&#x1D726;</mtext></math>=<span>1D726</span></span>
+ <span><math class="testfont"><mtext>&#x1D727;</mtext></math>=<span>1D727</span></span>
+ <span><math class="testfont"><mtext>&#x1D728;</mtext></math>=<span>1D728</span></span>
+ <span><math class="testfont"><mtext>&#x1D729;</mtext></math>=<span>1D729</span></span>
+ <span><math class="testfont"><mtext>&#x1D72A;</mtext></math>=<span>1D72A</span></span>
+ <span><math class="testfont"><mtext>&#x1D72B;</mtext></math>=<span>1D72B</span></span>
+ <span><math class="testfont"><mtext>&#x1D72C;</mtext></math>=<span>1D72C</span></span>
+ <span><math class="testfont"><mtext>&#x1D72D;</mtext></math>=<span>1D72D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D72E;</mtext></math>=<span>1D72E</span></span>
+ <span><math class="testfont"><mtext>&#x1D72F;</mtext></math>=<span>1D72F</span></span>
+ <span><math class="testfont"><mtext>&#x1D730;</mtext></math>=<span>1D730</span></span>
+ <span><math class="testfont"><mtext>&#x1D731;</mtext></math>=<span>1D731</span></span>
+ <span><math class="testfont"><mtext>&#x1D732;</mtext></math>=<span>1D732</span></span>
+ <span><math class="testfont"><mtext>&#x1D733;</mtext></math>=<span>1D733</span></span>
+ <span><math class="testfont"><mtext>&#x1D734;</mtext></math>=<span>1D734</span></span>
+ <span><math class="testfont"><mtext>&#x1D735;</mtext></math>=<span>1D735</span></span>
+ <span><math class="testfont"><mtext>&#x1D736;</mtext></math>=<span>1D736</span></span>
+ <span><math class="testfont"><mtext>&#x1D737;</mtext></math>=<span>1D737</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D738;</mtext></math>=<span>1D738</span></span>
+ <span><math class="testfont"><mtext>&#x1D739;</mtext></math>=<span>1D739</span></span>
+ <span><math class="testfont"><mtext>&#x1D73A;</mtext></math>=<span>1D73A</span></span>
+ <span><math class="testfont"><mtext>&#x1D73B;</mtext></math>=<span>1D73B</span></span>
+ <span><math class="testfont"><mtext>&#x1D73C;</mtext></math>=<span>1D73C</span></span>
+ <span><math class="testfont"><mtext>&#x1D73D;</mtext></math>=<span>1D73D</span></span>
+ <span><math class="testfont"><mtext>&#x1D73E;</mtext></math>=<span>1D73E</span></span>
+ <span><math class="testfont"><mtext>&#x1D73F;</mtext></math>=<span>1D73F</span></span>
+ <span><math class="testfont"><mtext>&#x1D740;</mtext></math>=<span>1D740</span></span>
+ <span><math class="testfont"><mtext>&#x1D741;</mtext></math>=<span>1D741</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D742;</mtext></math>=<span>1D742</span></span>
+ <span><math class="testfont"><mtext>&#x1D743;</mtext></math>=<span>1D743</span></span>
+ <span><math class="testfont"><mtext>&#x1D744;</mtext></math>=<span>1D744</span></span>
+ <span><math class="testfont"><mtext>&#x1D745;</mtext></math>=<span>1D745</span></span>
+ <span><math class="testfont"><mtext>&#x1D746;</mtext></math>=<span>1D746</span></span>
+ <span><math class="testfont"><mtext>&#x1D747;</mtext></math>=<span>1D747</span></span>
+ <span><math class="testfont"><mtext>&#x1D748;</mtext></math>=<span>1D748</span></span>
+ <span><math class="testfont"><mtext>&#x1D749;</mtext></math>=<span>1D749</span></span>
+ <span><math class="testfont"><mtext>&#x1D74A;</mtext></math>=<span>1D74A</span></span>
+ <span><math class="testfont"><mtext>&#x1D74B;</mtext></math>=<span>1D74B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D74C;</mtext></math>=<span>1D74C</span></span>
+ <span><math class="testfont"><mtext>&#x1D74D;</mtext></math>=<span>1D74D</span></span>
+ <span><math class="testfont"><mtext>&#x1D74E;</mtext></math>=<span>1D74E</span></span>
+ <span><math class="testfont"><mtext>&#x1D74F;</mtext></math>=<span>1D74F</span></span>
+ <span><math class="testfont"><mtext>&#x1D750;</mtext></math>=<span>1D750</span></span>
+ <span><math class="testfont"><mtext>&#x1D751;</mtext></math>=<span>1D751</span></span>
+ <span><math class="testfont"><mtext>&#x1D752;</mtext></math>=<span>1D752</span></span>
+ <span><math class="testfont"><mtext>&#x1D753;</mtext></math>=<span>1D753</span></span>
+ <span><math class="testfont"><mtext>&#x1D754;</mtext></math>=<span>1D754</span></span>
+ <span><math class="testfont"><mtext>&#x1D755;</mtext></math>=<span>1D755</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic.html
new file mode 100644
index 0000000000..725559a571
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-italic.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-italic</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#bold-italic-mappings">
+<link rel="match" href="mathvariant-bold-italic-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a bold-italic mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x41;</mtext></math>=<span>1D468</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x42;</mtext></math>=<span>1D469</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x43;</mtext></math>=<span>1D46A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x44;</mtext></math>=<span>1D46B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x45;</mtext></math>=<span>1D46C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x46;</mtext></math>=<span>1D46D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x47;</mtext></math>=<span>1D46E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x48;</mtext></math>=<span>1D46F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x49;</mtext></math>=<span>1D470</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x4A;</mtext></math>=<span>1D471</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x4B;</mtext></math>=<span>1D472</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x4C;</mtext></math>=<span>1D473</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x4D;</mtext></math>=<span>1D474</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x4E;</mtext></math>=<span>1D475</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x4F;</mtext></math>=<span>1D476</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x50;</mtext></math>=<span>1D477</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x51;</mtext></math>=<span>1D478</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x52;</mtext></math>=<span>1D479</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x53;</mtext></math>=<span>1D47A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x54;</mtext></math>=<span>1D47B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x55;</mtext></math>=<span>1D47C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x56;</mtext></math>=<span>1D47D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x57;</mtext></math>=<span>1D47E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x58;</mtext></math>=<span>1D47F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x59;</mtext></math>=<span>1D480</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x5A;</mtext></math>=<span>1D481</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x61;</mtext></math>=<span>1D482</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x62;</mtext></math>=<span>1D483</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x63;</mtext></math>=<span>1D484</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x64;</mtext></math>=<span>1D485</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x65;</mtext></math>=<span>1D486</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x66;</mtext></math>=<span>1D487</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x67;</mtext></math>=<span>1D488</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x68;</mtext></math>=<span>1D489</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x69;</mtext></math>=<span>1D48A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x6A;</mtext></math>=<span>1D48B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x6B;</mtext></math>=<span>1D48C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x6C;</mtext></math>=<span>1D48D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x6D;</mtext></math>=<span>1D48E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x6E;</mtext></math>=<span>1D48F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x6F;</mtext></math>=<span>1D490</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x70;</mtext></math>=<span>1D491</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x71;</mtext></math>=<span>1D492</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x72;</mtext></math>=<span>1D493</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x73;</mtext></math>=<span>1D494</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x74;</mtext></math>=<span>1D495</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x75;</mtext></math>=<span>1D496</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x76;</mtext></math>=<span>1D497</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x77;</mtext></math>=<span>1D498</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x78;</mtext></math>=<span>1D499</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x79;</mtext></math>=<span>1D49A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x7A;</mtext></math>=<span>1D49B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x391;</mtext></math>=<span>1D71C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x392;</mtext></math>=<span>1D71D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x393;</mtext></math>=<span>1D71E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x394;</mtext></math>=<span>1D71F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x395;</mtext></math>=<span>1D720</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x396;</mtext></math>=<span>1D721</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x397;</mtext></math>=<span>1D722</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x398;</mtext></math>=<span>1D723</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x399;</mtext></math>=<span>1D724</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x39A;</mtext></math>=<span>1D725</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x39B;</mtext></math>=<span>1D726</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x39C;</mtext></math>=<span>1D727</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x39D;</mtext></math>=<span>1D728</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x39E;</mtext></math>=<span>1D729</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x39F;</mtext></math>=<span>1D72A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A0;</mtext></math>=<span>1D72B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A1;</mtext></math>=<span>1D72C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3F4;</mtext></math>=<span>1D72D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A3;</mtext></math>=<span>1D72E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A4;</mtext></math>=<span>1D72F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A5;</mtext></math>=<span>1D730</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A6;</mtext></math>=<span>1D731</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A7;</mtext></math>=<span>1D732</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A8;</mtext></math>=<span>1D733</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3A9;</mtext></math>=<span>1D734</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x2207;</mtext></math>=<span>1D735</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B1;</mtext></math>=<span>1D736</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B2;</mtext></math>=<span>1D737</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B3;</mtext></math>=<span>1D738</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B4;</mtext></math>=<span>1D739</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B5;</mtext></math>=<span>1D73A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B6;</mtext></math>=<span>1D73B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B7;</mtext></math>=<span>1D73C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B8;</mtext></math>=<span>1D73D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3B9;</mtext></math>=<span>1D73E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3BA;</mtext></math>=<span>1D73F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3BB;</mtext></math>=<span>1D740</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3BC;</mtext></math>=<span>1D741</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3BD;</mtext></math>=<span>1D742</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3BE;</mtext></math>=<span>1D743</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3BF;</mtext></math>=<span>1D744</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C0;</mtext></math>=<span>1D745</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C1;</mtext></math>=<span>1D746</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C2;</mtext></math>=<span>1D747</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C3;</mtext></math>=<span>1D748</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C4;</mtext></math>=<span>1D749</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C5;</mtext></math>=<span>1D74A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C6;</mtext></math>=<span>1D74B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C7;</mtext></math>=<span>1D74C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C8;</mtext></math>=<span>1D74D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3C9;</mtext></math>=<span>1D74E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x2202;</mtext></math>=<span>1D74F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3F5;</mtext></math>=<span>1D750</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3D1;</mtext></math>=<span>1D751</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3F0;</mtext></math>=<span>1D752</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3D5;</mtext></math>=<span>1D753</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3F1;</mtext></math>=<span>1D754</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-italic">&#x3D6;</mtext></math>=<span>1D755</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-ref.html
new file mode 100644
index 0000000000..ac24ab5b00
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-ref.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D400;</mtext></math>=<span>1D400</span></span>
+ <span><math class="testfont"><mtext>&#x1D401;</mtext></math>=<span>1D401</span></span>
+ <span><math class="testfont"><mtext>&#x1D402;</mtext></math>=<span>1D402</span></span>
+ <span><math class="testfont"><mtext>&#x1D403;</mtext></math>=<span>1D403</span></span>
+ <span><math class="testfont"><mtext>&#x1D404;</mtext></math>=<span>1D404</span></span>
+ <span><math class="testfont"><mtext>&#x1D405;</mtext></math>=<span>1D405</span></span>
+ <span><math class="testfont"><mtext>&#x1D406;</mtext></math>=<span>1D406</span></span>
+ <span><math class="testfont"><mtext>&#x1D407;</mtext></math>=<span>1D407</span></span>
+ <span><math class="testfont"><mtext>&#x1D408;</mtext></math>=<span>1D408</span></span>
+ <span><math class="testfont"><mtext>&#x1D409;</mtext></math>=<span>1D409</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D40A;</mtext></math>=<span>1D40A</span></span>
+ <span><math class="testfont"><mtext>&#x1D40B;</mtext></math>=<span>1D40B</span></span>
+ <span><math class="testfont"><mtext>&#x1D40C;</mtext></math>=<span>1D40C</span></span>
+ <span><math class="testfont"><mtext>&#x1D40D;</mtext></math>=<span>1D40D</span></span>
+ <span><math class="testfont"><mtext>&#x1D40E;</mtext></math>=<span>1D40E</span></span>
+ <span><math class="testfont"><mtext>&#x1D40F;</mtext></math>=<span>1D40F</span></span>
+ <span><math class="testfont"><mtext>&#x1D410;</mtext></math>=<span>1D410</span></span>
+ <span><math class="testfont"><mtext>&#x1D411;</mtext></math>=<span>1D411</span></span>
+ <span><math class="testfont"><mtext>&#x1D412;</mtext></math>=<span>1D412</span></span>
+ <span><math class="testfont"><mtext>&#x1D413;</mtext></math>=<span>1D413</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D414;</mtext></math>=<span>1D414</span></span>
+ <span><math class="testfont"><mtext>&#x1D415;</mtext></math>=<span>1D415</span></span>
+ <span><math class="testfont"><mtext>&#x1D416;</mtext></math>=<span>1D416</span></span>
+ <span><math class="testfont"><mtext>&#x1D417;</mtext></math>=<span>1D417</span></span>
+ <span><math class="testfont"><mtext>&#x1D418;</mtext></math>=<span>1D418</span></span>
+ <span><math class="testfont"><mtext>&#x1D419;</mtext></math>=<span>1D419</span></span>
+ <span><math class="testfont"><mtext>&#x1D41A;</mtext></math>=<span>1D41A</span></span>
+ <span><math class="testfont"><mtext>&#x1D41B;</mtext></math>=<span>1D41B</span></span>
+ <span><math class="testfont"><mtext>&#x1D41C;</mtext></math>=<span>1D41C</span></span>
+ <span><math class="testfont"><mtext>&#x1D41D;</mtext></math>=<span>1D41D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D41E;</mtext></math>=<span>1D41E</span></span>
+ <span><math class="testfont"><mtext>&#x1D41F;</mtext></math>=<span>1D41F</span></span>
+ <span><math class="testfont"><mtext>&#x1D420;</mtext></math>=<span>1D420</span></span>
+ <span><math class="testfont"><mtext>&#x1D421;</mtext></math>=<span>1D421</span></span>
+ <span><math class="testfont"><mtext>&#x1D422;</mtext></math>=<span>1D422</span></span>
+ <span><math class="testfont"><mtext>&#x1D423;</mtext></math>=<span>1D423</span></span>
+ <span><math class="testfont"><mtext>&#x1D424;</mtext></math>=<span>1D424</span></span>
+ <span><math class="testfont"><mtext>&#x1D425;</mtext></math>=<span>1D425</span></span>
+ <span><math class="testfont"><mtext>&#x1D426;</mtext></math>=<span>1D426</span></span>
+ <span><math class="testfont"><mtext>&#x1D427;</mtext></math>=<span>1D427</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D428;</mtext></math>=<span>1D428</span></span>
+ <span><math class="testfont"><mtext>&#x1D429;</mtext></math>=<span>1D429</span></span>
+ <span><math class="testfont"><mtext>&#x1D42A;</mtext></math>=<span>1D42A</span></span>
+ <span><math class="testfont"><mtext>&#x1D42B;</mtext></math>=<span>1D42B</span></span>
+ <span><math class="testfont"><mtext>&#x1D42C;</mtext></math>=<span>1D42C</span></span>
+ <span><math class="testfont"><mtext>&#x1D42D;</mtext></math>=<span>1D42D</span></span>
+ <span><math class="testfont"><mtext>&#x1D42E;</mtext></math>=<span>1D42E</span></span>
+ <span><math class="testfont"><mtext>&#x1D42F;</mtext></math>=<span>1D42F</span></span>
+ <span><math class="testfont"><mtext>&#x1D430;</mtext></math>=<span>1D430</span></span>
+ <span><math class="testfont"><mtext>&#x1D431;</mtext></math>=<span>1D431</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D432;</mtext></math>=<span>1D432</span></span>
+ <span><math class="testfont"><mtext>&#x1D433;</mtext></math>=<span>1D433</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A8;</mtext></math>=<span>1D6A8</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A9;</mtext></math>=<span>1D6A9</span></span>
+ <span><math class="testfont"><mtext>&#x1D6AA;</mtext></math>=<span>1D6AA</span></span>
+ <span><math class="testfont"><mtext>&#x1D6AB;</mtext></math>=<span>1D6AB</span></span>
+ <span><math class="testfont"><mtext>&#x1D6AC;</mtext></math>=<span>1D6AC</span></span>
+ <span><math class="testfont"><mtext>&#x1D6AD;</mtext></math>=<span>1D6AD</span></span>
+ <span><math class="testfont"><mtext>&#x1D6AE;</mtext></math>=<span>1D6AE</span></span>
+ <span><math class="testfont"><mtext>&#x1D6AF;</mtext></math>=<span>1D6AF</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6B0;</mtext></math>=<span>1D6B0</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B1;</mtext></math>=<span>1D6B1</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B2;</mtext></math>=<span>1D6B2</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B3;</mtext></math>=<span>1D6B3</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B4;</mtext></math>=<span>1D6B4</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B5;</mtext></math>=<span>1D6B5</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B6;</mtext></math>=<span>1D6B6</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B7;</mtext></math>=<span>1D6B7</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B8;</mtext></math>=<span>1D6B8</span></span>
+ <span><math class="testfont"><mtext>&#x1D6B9;</mtext></math>=<span>1D6B9</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6BA;</mtext></math>=<span>1D6BA</span></span>
+ <span><math class="testfont"><mtext>&#x1D6BB;</mtext></math>=<span>1D6BB</span></span>
+ <span><math class="testfont"><mtext>&#x1D6BC;</mtext></math>=<span>1D6BC</span></span>
+ <span><math class="testfont"><mtext>&#x1D6BD;</mtext></math>=<span>1D6BD</span></span>
+ <span><math class="testfont"><mtext>&#x1D6BE;</mtext></math>=<span>1D6BE</span></span>
+ <span><math class="testfont"><mtext>&#x1D6BF;</mtext></math>=<span>1D6BF</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C0;</mtext></math>=<span>1D6C0</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C1;</mtext></math>=<span>1D6C1</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C2;</mtext></math>=<span>1D6C2</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C3;</mtext></math>=<span>1D6C3</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6C4;</mtext></math>=<span>1D6C4</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C5;</mtext></math>=<span>1D6C5</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C6;</mtext></math>=<span>1D6C6</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C7;</mtext></math>=<span>1D6C7</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C8;</mtext></math>=<span>1D6C8</span></span>
+ <span><math class="testfont"><mtext>&#x1D6C9;</mtext></math>=<span>1D6C9</span></span>
+ <span><math class="testfont"><mtext>&#x1D6CA;</mtext></math>=<span>1D6CA</span></span>
+ <span><math class="testfont"><mtext>&#x1D6CB;</mtext></math>=<span>1D6CB</span></span>
+ <span><math class="testfont"><mtext>&#x1D6CC;</mtext></math>=<span>1D6CC</span></span>
+ <span><math class="testfont"><mtext>&#x1D6CD;</mtext></math>=<span>1D6CD</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6CE;</mtext></math>=<span>1D6CE</span></span>
+ <span><math class="testfont"><mtext>&#x1D6CF;</mtext></math>=<span>1D6CF</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D0;</mtext></math>=<span>1D6D0</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D1;</mtext></math>=<span>1D6D1</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D2;</mtext></math>=<span>1D6D2</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D3;</mtext></math>=<span>1D6D3</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D4;</mtext></math>=<span>1D6D4</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D5;</mtext></math>=<span>1D6D5</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D6;</mtext></math>=<span>1D6D6</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D7;</mtext></math>=<span>1D6D7</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6D8;</mtext></math>=<span>1D6D8</span></span>
+ <span><math class="testfont"><mtext>&#x1D6D9;</mtext></math>=<span>1D6D9</span></span>
+ <span><math class="testfont"><mtext>&#x1D6DA;</mtext></math>=<span>1D6DA</span></span>
+ <span><math class="testfont"><mtext>&#x1D6DB;</mtext></math>=<span>1D6DB</span></span>
+ <span><math class="testfont"><mtext>&#x1D6DC;</mtext></math>=<span>1D6DC</span></span>
+ <span><math class="testfont"><mtext>&#x1D6DD;</mtext></math>=<span>1D6DD</span></span>
+ <span><math class="testfont"><mtext>&#x1D6DE;</mtext></math>=<span>1D6DE</span></span>
+ <span><math class="testfont"><mtext>&#x1D6DF;</mtext></math>=<span>1D6DF</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E0;</mtext></math>=<span>1D6E0</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E1;</mtext></math>=<span>1D6E1</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7CA;</mtext></math>=<span>1D7CA</span></span>
+ <span><math class="testfont"><mtext>&#x1D7CB;</mtext></math>=<span>1D7CB</span></span>
+ <span><math class="testfont"><mtext>&#x1D7CE;</mtext></math>=<span>1D7CE</span></span>
+ <span><math class="testfont"><mtext>&#x1D7CF;</mtext></math>=<span>1D7CF</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D0;</mtext></math>=<span>1D7D0</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D1;</mtext></math>=<span>1D7D1</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D2;</mtext></math>=<span>1D7D2</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D3;</mtext></math>=<span>1D7D3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D4;</mtext></math>=<span>1D7D4</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D5;</mtext></math>=<span>1D7D5</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7D6;</mtext></math>=<span>1D7D6</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D7;</mtext></math>=<span>1D7D7</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif-ref.html
new file mode 100644
index 0000000000..78c0cc5ea6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif-ref.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-sans-serif (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-sans-serif.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D5D4;</mtext></math>=<span>1D5D4</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D5;</mtext></math>=<span>1D5D5</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D6;</mtext></math>=<span>1D5D6</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D7;</mtext></math>=<span>1D5D7</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D8;</mtext></math>=<span>1D5D8</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D9;</mtext></math>=<span>1D5D9</span></span>
+ <span><math class="testfont"><mtext>&#x1D5DA;</mtext></math>=<span>1D5DA</span></span>
+ <span><math class="testfont"><mtext>&#x1D5DB;</mtext></math>=<span>1D5DB</span></span>
+ <span><math class="testfont"><mtext>&#x1D5DC;</mtext></math>=<span>1D5DC</span></span>
+ <span><math class="testfont"><mtext>&#x1D5DD;</mtext></math>=<span>1D5DD</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5DE;</mtext></math>=<span>1D5DE</span></span>
+ <span><math class="testfont"><mtext>&#x1D5DF;</mtext></math>=<span>1D5DF</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E0;</mtext></math>=<span>1D5E0</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E1;</mtext></math>=<span>1D5E1</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E2;</mtext></math>=<span>1D5E2</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E3;</mtext></math>=<span>1D5E3</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E4;</mtext></math>=<span>1D5E4</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E5;</mtext></math>=<span>1D5E5</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E6;</mtext></math>=<span>1D5E6</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E7;</mtext></math>=<span>1D5E7</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5E8;</mtext></math>=<span>1D5E8</span></span>
+ <span><math class="testfont"><mtext>&#x1D5E9;</mtext></math>=<span>1D5E9</span></span>
+ <span><math class="testfont"><mtext>&#x1D5EA;</mtext></math>=<span>1D5EA</span></span>
+ <span><math class="testfont"><mtext>&#x1D5EB;</mtext></math>=<span>1D5EB</span></span>
+ <span><math class="testfont"><mtext>&#x1D5EC;</mtext></math>=<span>1D5EC</span></span>
+ <span><math class="testfont"><mtext>&#x1D5ED;</mtext></math>=<span>1D5ED</span></span>
+ <span><math class="testfont"><mtext>&#x1D5EE;</mtext></math>=<span>1D5EE</span></span>
+ <span><math class="testfont"><mtext>&#x1D5EF;</mtext></math>=<span>1D5EF</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F0;</mtext></math>=<span>1D5F0</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F1;</mtext></math>=<span>1D5F1</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5F2;</mtext></math>=<span>1D5F2</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F3;</mtext></math>=<span>1D5F3</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F4;</mtext></math>=<span>1D5F4</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F5;</mtext></math>=<span>1D5F5</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F6;</mtext></math>=<span>1D5F6</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F7;</mtext></math>=<span>1D5F7</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F8;</mtext></math>=<span>1D5F8</span></span>
+ <span><math class="testfont"><mtext>&#x1D5F9;</mtext></math>=<span>1D5F9</span></span>
+ <span><math class="testfont"><mtext>&#x1D5FA;</mtext></math>=<span>1D5FA</span></span>
+ <span><math class="testfont"><mtext>&#x1D5FB;</mtext></math>=<span>1D5FB</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5FC;</mtext></math>=<span>1D5FC</span></span>
+ <span><math class="testfont"><mtext>&#x1D5FD;</mtext></math>=<span>1D5FD</span></span>
+ <span><math class="testfont"><mtext>&#x1D5FE;</mtext></math>=<span>1D5FE</span></span>
+ <span><math class="testfont"><mtext>&#x1D5FF;</mtext></math>=<span>1D5FF</span></span>
+ <span><math class="testfont"><mtext>&#x1D600;</mtext></math>=<span>1D600</span></span>
+ <span><math class="testfont"><mtext>&#x1D601;</mtext></math>=<span>1D601</span></span>
+ <span><math class="testfont"><mtext>&#x1D602;</mtext></math>=<span>1D602</span></span>
+ <span><math class="testfont"><mtext>&#x1D603;</mtext></math>=<span>1D603</span></span>
+ <span><math class="testfont"><mtext>&#x1D604;</mtext></math>=<span>1D604</span></span>
+ <span><math class="testfont"><mtext>&#x1D605;</mtext></math>=<span>1D605</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D606;</mtext></math>=<span>1D606</span></span>
+ <span><math class="testfont"><mtext>&#x1D607;</mtext></math>=<span>1D607</span></span>
+ <span><math class="testfont"><mtext>&#x1D756;</mtext></math>=<span>1D756</span></span>
+ <span><math class="testfont"><mtext>&#x1D757;</mtext></math>=<span>1D757</span></span>
+ <span><math class="testfont"><mtext>&#x1D758;</mtext></math>=<span>1D758</span></span>
+ <span><math class="testfont"><mtext>&#x1D759;</mtext></math>=<span>1D759</span></span>
+ <span><math class="testfont"><mtext>&#x1D75A;</mtext></math>=<span>1D75A</span></span>
+ <span><math class="testfont"><mtext>&#x1D75B;</mtext></math>=<span>1D75B</span></span>
+ <span><math class="testfont"><mtext>&#x1D75C;</mtext></math>=<span>1D75C</span></span>
+ <span><math class="testfont"><mtext>&#x1D75D;</mtext></math>=<span>1D75D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D75E;</mtext></math>=<span>1D75E</span></span>
+ <span><math class="testfont"><mtext>&#x1D75F;</mtext></math>=<span>1D75F</span></span>
+ <span><math class="testfont"><mtext>&#x1D760;</mtext></math>=<span>1D760</span></span>
+ <span><math class="testfont"><mtext>&#x1D761;</mtext></math>=<span>1D761</span></span>
+ <span><math class="testfont"><mtext>&#x1D762;</mtext></math>=<span>1D762</span></span>
+ <span><math class="testfont"><mtext>&#x1D763;</mtext></math>=<span>1D763</span></span>
+ <span><math class="testfont"><mtext>&#x1D764;</mtext></math>=<span>1D764</span></span>
+ <span><math class="testfont"><mtext>&#x1D765;</mtext></math>=<span>1D765</span></span>
+ <span><math class="testfont"><mtext>&#x1D766;</mtext></math>=<span>1D766</span></span>
+ <span><math class="testfont"><mtext>&#x1D767;</mtext></math>=<span>1D767</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D768;</mtext></math>=<span>1D768</span></span>
+ <span><math class="testfont"><mtext>&#x1D769;</mtext></math>=<span>1D769</span></span>
+ <span><math class="testfont"><mtext>&#x1D76A;</mtext></math>=<span>1D76A</span></span>
+ <span><math class="testfont"><mtext>&#x1D76B;</mtext></math>=<span>1D76B</span></span>
+ <span><math class="testfont"><mtext>&#x1D76C;</mtext></math>=<span>1D76C</span></span>
+ <span><math class="testfont"><mtext>&#x1D76D;</mtext></math>=<span>1D76D</span></span>
+ <span><math class="testfont"><mtext>&#x1D76E;</mtext></math>=<span>1D76E</span></span>
+ <span><math class="testfont"><mtext>&#x1D76F;</mtext></math>=<span>1D76F</span></span>
+ <span><math class="testfont"><mtext>&#x1D770;</mtext></math>=<span>1D770</span></span>
+ <span><math class="testfont"><mtext>&#x1D771;</mtext></math>=<span>1D771</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D772;</mtext></math>=<span>1D772</span></span>
+ <span><math class="testfont"><mtext>&#x1D773;</mtext></math>=<span>1D773</span></span>
+ <span><math class="testfont"><mtext>&#x1D774;</mtext></math>=<span>1D774</span></span>
+ <span><math class="testfont"><mtext>&#x1D775;</mtext></math>=<span>1D775</span></span>
+ <span><math class="testfont"><mtext>&#x1D776;</mtext></math>=<span>1D776</span></span>
+ <span><math class="testfont"><mtext>&#x1D777;</mtext></math>=<span>1D777</span></span>
+ <span><math class="testfont"><mtext>&#x1D778;</mtext></math>=<span>1D778</span></span>
+ <span><math class="testfont"><mtext>&#x1D779;</mtext></math>=<span>1D779</span></span>
+ <span><math class="testfont"><mtext>&#x1D77A;</mtext></math>=<span>1D77A</span></span>
+ <span><math class="testfont"><mtext>&#x1D77B;</mtext></math>=<span>1D77B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D77C;</mtext></math>=<span>1D77C</span></span>
+ <span><math class="testfont"><mtext>&#x1D77D;</mtext></math>=<span>1D77D</span></span>
+ <span><math class="testfont"><mtext>&#x1D77E;</mtext></math>=<span>1D77E</span></span>
+ <span><math class="testfont"><mtext>&#x1D77F;</mtext></math>=<span>1D77F</span></span>
+ <span><math class="testfont"><mtext>&#x1D780;</mtext></math>=<span>1D780</span></span>
+ <span><math class="testfont"><mtext>&#x1D781;</mtext></math>=<span>1D781</span></span>
+ <span><math class="testfont"><mtext>&#x1D782;</mtext></math>=<span>1D782</span></span>
+ <span><math class="testfont"><mtext>&#x1D783;</mtext></math>=<span>1D783</span></span>
+ <span><math class="testfont"><mtext>&#x1D784;</mtext></math>=<span>1D784</span></span>
+ <span><math class="testfont"><mtext>&#x1D785;</mtext></math>=<span>1D785</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D786;</mtext></math>=<span>1D786</span></span>
+ <span><math class="testfont"><mtext>&#x1D787;</mtext></math>=<span>1D787</span></span>
+ <span><math class="testfont"><mtext>&#x1D788;</mtext></math>=<span>1D788</span></span>
+ <span><math class="testfont"><mtext>&#x1D789;</mtext></math>=<span>1D789</span></span>
+ <span><math class="testfont"><mtext>&#x1D78A;</mtext></math>=<span>1D78A</span></span>
+ <span><math class="testfont"><mtext>&#x1D78B;</mtext></math>=<span>1D78B</span></span>
+ <span><math class="testfont"><mtext>&#x1D78C;</mtext></math>=<span>1D78C</span></span>
+ <span><math class="testfont"><mtext>&#x1D78D;</mtext></math>=<span>1D78D</span></span>
+ <span><math class="testfont"><mtext>&#x1D78E;</mtext></math>=<span>1D78E</span></span>
+ <span><math class="testfont"><mtext>&#x1D78F;</mtext></math>=<span>1D78F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7EC;</mtext></math>=<span>1D7EC</span></span>
+ <span><math class="testfont"><mtext>&#x1D7ED;</mtext></math>=<span>1D7ED</span></span>
+ <span><math class="testfont"><mtext>&#x1D7EE;</mtext></math>=<span>1D7EE</span></span>
+ <span><math class="testfont"><mtext>&#x1D7EF;</mtext></math>=<span>1D7EF</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F0;</mtext></math>=<span>1D7F0</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F1;</mtext></math>=<span>1D7F1</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F2;</mtext></math>=<span>1D7F2</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F3;</mtext></math>=<span>1D7F3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F4;</mtext></math>=<span>1D7F4</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F5;</mtext></math>=<span>1D7F5</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif.html
new file mode 100644
index 0000000000..0e0662dff6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-sans-serif.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-sans-serif</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#bold-sans-serif-mappings">
+<link rel="match" href="mathvariant-bold-sans-serif-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a bold-sans-serif mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-sans-serif.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x41;</mtext></math>=<span>1D5D4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x42;</mtext></math>=<span>1D5D5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x43;</mtext></math>=<span>1D5D6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x44;</mtext></math>=<span>1D5D7</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x45;</mtext></math>=<span>1D5D8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x46;</mtext></math>=<span>1D5D9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x47;</mtext></math>=<span>1D5DA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x48;</mtext></math>=<span>1D5DB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x49;</mtext></math>=<span>1D5DC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x4A;</mtext></math>=<span>1D5DD</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x4B;</mtext></math>=<span>1D5DE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x4C;</mtext></math>=<span>1D5DF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x4D;</mtext></math>=<span>1D5E0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x4E;</mtext></math>=<span>1D5E1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x4F;</mtext></math>=<span>1D5E2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x50;</mtext></math>=<span>1D5E3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x51;</mtext></math>=<span>1D5E4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x52;</mtext></math>=<span>1D5E5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x53;</mtext></math>=<span>1D5E6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x54;</mtext></math>=<span>1D5E7</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x55;</mtext></math>=<span>1D5E8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x56;</mtext></math>=<span>1D5E9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x57;</mtext></math>=<span>1D5EA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x58;</mtext></math>=<span>1D5EB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x59;</mtext></math>=<span>1D5EC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x5A;</mtext></math>=<span>1D5ED</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x61;</mtext></math>=<span>1D5EE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x62;</mtext></math>=<span>1D5EF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x63;</mtext></math>=<span>1D5F0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x64;</mtext></math>=<span>1D5F1</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x65;</mtext></math>=<span>1D5F2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x66;</mtext></math>=<span>1D5F3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x67;</mtext></math>=<span>1D5F4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x68;</mtext></math>=<span>1D5F5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x69;</mtext></math>=<span>1D5F6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x6A;</mtext></math>=<span>1D5F7</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x6B;</mtext></math>=<span>1D5F8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x6C;</mtext></math>=<span>1D5F9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x6D;</mtext></math>=<span>1D5FA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x6E;</mtext></math>=<span>1D5FB</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x6F;</mtext></math>=<span>1D5FC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x70;</mtext></math>=<span>1D5FD</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x71;</mtext></math>=<span>1D5FE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x72;</mtext></math>=<span>1D5FF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x73;</mtext></math>=<span>1D600</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x74;</mtext></math>=<span>1D601</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x75;</mtext></math>=<span>1D602</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x76;</mtext></math>=<span>1D603</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x77;</mtext></math>=<span>1D604</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x78;</mtext></math>=<span>1D605</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x79;</mtext></math>=<span>1D606</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x7A;</mtext></math>=<span>1D607</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x391;</mtext></math>=<span>1D756</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x392;</mtext></math>=<span>1D757</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x393;</mtext></math>=<span>1D758</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x394;</mtext></math>=<span>1D759</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x395;</mtext></math>=<span>1D75A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x396;</mtext></math>=<span>1D75B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x397;</mtext></math>=<span>1D75C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x398;</mtext></math>=<span>1D75D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x399;</mtext></math>=<span>1D75E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39A;</mtext></math>=<span>1D75F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39B;</mtext></math>=<span>1D760</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39C;</mtext></math>=<span>1D761</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39D;</mtext></math>=<span>1D762</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39E;</mtext></math>=<span>1D763</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39F;</mtext></math>=<span>1D764</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A0;</mtext></math>=<span>1D765</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A1;</mtext></math>=<span>1D766</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3F4;</mtext></math>=<span>1D767</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A3;</mtext></math>=<span>1D768</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A4;</mtext></math>=<span>1D769</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A5;</mtext></math>=<span>1D76A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A6;</mtext></math>=<span>1D76B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A7;</mtext></math>=<span>1D76C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A8;</mtext></math>=<span>1D76D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3A9;</mtext></math>=<span>1D76E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x2207;</mtext></math>=<span>1D76F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B1;</mtext></math>=<span>1D770</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B2;</mtext></math>=<span>1D771</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B3;</mtext></math>=<span>1D772</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B4;</mtext></math>=<span>1D773</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B5;</mtext></math>=<span>1D774</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B6;</mtext></math>=<span>1D775</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B7;</mtext></math>=<span>1D776</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B8;</mtext></math>=<span>1D777</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3B9;</mtext></math>=<span>1D778</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3BA;</mtext></math>=<span>1D779</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3BB;</mtext></math>=<span>1D77A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3BC;</mtext></math>=<span>1D77B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3BD;</mtext></math>=<span>1D77C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3BE;</mtext></math>=<span>1D77D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3BF;</mtext></math>=<span>1D77E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C0;</mtext></math>=<span>1D77F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C1;</mtext></math>=<span>1D780</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C2;</mtext></math>=<span>1D781</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C3;</mtext></math>=<span>1D782</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C4;</mtext></math>=<span>1D783</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C5;</mtext></math>=<span>1D784</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C6;</mtext></math>=<span>1D785</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C7;</mtext></math>=<span>1D786</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C8;</mtext></math>=<span>1D787</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3C9;</mtext></math>=<span>1D788</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x2202;</mtext></math>=<span>1D789</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3F5;</mtext></math>=<span>1D78A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3D1;</mtext></math>=<span>1D78B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3F0;</mtext></math>=<span>1D78C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3D5;</mtext></math>=<span>1D78D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3F1;</mtext></math>=<span>1D78E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x3D6;</mtext></math>=<span>1D78F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x30;</mtext></math>=<span>1D7EC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x31;</mtext></math>=<span>1D7ED</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x32;</mtext></math>=<span>1D7EE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x33;</mtext></math>=<span>1D7EF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x34;</mtext></math>=<span>1D7F0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x35;</mtext></math>=<span>1D7F1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x36;</mtext></math>=<span>1D7F2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x37;</mtext></math>=<span>1D7F3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x38;</mtext></math>=<span>1D7F4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-sans-serif">&#x39;</mtext></math>=<span>1D7F5</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script-ref.html
new file mode 100644
index 0000000000..ef11f7fb8f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script-ref.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-script (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-script.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D4D0;</mtext></math>=<span>1D4D0</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D1;</mtext></math>=<span>1D4D1</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D2;</mtext></math>=<span>1D4D2</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D3;</mtext></math>=<span>1D4D3</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D4;</mtext></math>=<span>1D4D4</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D5;</mtext></math>=<span>1D4D5</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D6;</mtext></math>=<span>1D4D6</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D7;</mtext></math>=<span>1D4D7</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D8;</mtext></math>=<span>1D4D8</span></span>
+ <span><math class="testfont"><mtext>&#x1D4D9;</mtext></math>=<span>1D4D9</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4DA;</mtext></math>=<span>1D4DA</span></span>
+ <span><math class="testfont"><mtext>&#x1D4DB;</mtext></math>=<span>1D4DB</span></span>
+ <span><math class="testfont"><mtext>&#x1D4DC;</mtext></math>=<span>1D4DC</span></span>
+ <span><math class="testfont"><mtext>&#x1D4DD;</mtext></math>=<span>1D4DD</span></span>
+ <span><math class="testfont"><mtext>&#x1D4DE;</mtext></math>=<span>1D4DE</span></span>
+ <span><math class="testfont"><mtext>&#x1D4DF;</mtext></math>=<span>1D4DF</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E0;</mtext></math>=<span>1D4E0</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E1;</mtext></math>=<span>1D4E1</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E2;</mtext></math>=<span>1D4E2</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E3;</mtext></math>=<span>1D4E3</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4E4;</mtext></math>=<span>1D4E4</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E5;</mtext></math>=<span>1D4E5</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E6;</mtext></math>=<span>1D4E6</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E7;</mtext></math>=<span>1D4E7</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E8;</mtext></math>=<span>1D4E8</span></span>
+ <span><math class="testfont"><mtext>&#x1D4E9;</mtext></math>=<span>1D4E9</span></span>
+ <span><math class="testfont"><mtext>&#x1D4EA;</mtext></math>=<span>1D4EA</span></span>
+ <span><math class="testfont"><mtext>&#x1D4EB;</mtext></math>=<span>1D4EB</span></span>
+ <span><math class="testfont"><mtext>&#x1D4EC;</mtext></math>=<span>1D4EC</span></span>
+ <span><math class="testfont"><mtext>&#x1D4ED;</mtext></math>=<span>1D4ED</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4EE;</mtext></math>=<span>1D4EE</span></span>
+ <span><math class="testfont"><mtext>&#x1D4EF;</mtext></math>=<span>1D4EF</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F0;</mtext></math>=<span>1D4F0</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F1;</mtext></math>=<span>1D4F1</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F2;</mtext></math>=<span>1D4F2</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F3;</mtext></math>=<span>1D4F3</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F4;</mtext></math>=<span>1D4F4</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F5;</mtext></math>=<span>1D4F5</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F6;</mtext></math>=<span>1D4F6</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F7;</mtext></math>=<span>1D4F7</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4F8;</mtext></math>=<span>1D4F8</span></span>
+ <span><math class="testfont"><mtext>&#x1D4F9;</mtext></math>=<span>1D4F9</span></span>
+ <span><math class="testfont"><mtext>&#x1D4FA;</mtext></math>=<span>1D4FA</span></span>
+ <span><math class="testfont"><mtext>&#x1D4FB;</mtext></math>=<span>1D4FB</span></span>
+ <span><math class="testfont"><mtext>&#x1D4FC;</mtext></math>=<span>1D4FC</span></span>
+ <span><math class="testfont"><mtext>&#x1D4FD;</mtext></math>=<span>1D4FD</span></span>
+ <span><math class="testfont"><mtext>&#x1D4FE;</mtext></math>=<span>1D4FE</span></span>
+ <span><math class="testfont"><mtext>&#x1D4FF;</mtext></math>=<span>1D4FF</span></span>
+ <span><math class="testfont"><mtext>&#x1D500;</mtext></math>=<span>1D500</span></span>
+ <span><math class="testfont"><mtext>&#x1D501;</mtext></math>=<span>1D501</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D502;</mtext></math>=<span>1D502</span></span>
+ <span><math class="testfont"><mtext>&#x1D503;</mtext></math>=<span>1D503</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script.html
new file mode 100644
index 0000000000..0e775949f9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold-script.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold-script</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#bold-script-mappings">
+<link rel="match" href="mathvariant-bold-script-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a bold-script mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold-script.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x41;</mtext></math>=<span>1D4D0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x42;</mtext></math>=<span>1D4D1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x43;</mtext></math>=<span>1D4D2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x44;</mtext></math>=<span>1D4D3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x45;</mtext></math>=<span>1D4D4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x46;</mtext></math>=<span>1D4D5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x47;</mtext></math>=<span>1D4D6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x48;</mtext></math>=<span>1D4D7</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x49;</mtext></math>=<span>1D4D8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x4A;</mtext></math>=<span>1D4D9</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x4B;</mtext></math>=<span>1D4DA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x4C;</mtext></math>=<span>1D4DB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x4D;</mtext></math>=<span>1D4DC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x4E;</mtext></math>=<span>1D4DD</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x4F;</mtext></math>=<span>1D4DE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x50;</mtext></math>=<span>1D4DF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x51;</mtext></math>=<span>1D4E0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x52;</mtext></math>=<span>1D4E1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x53;</mtext></math>=<span>1D4E2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x54;</mtext></math>=<span>1D4E3</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x55;</mtext></math>=<span>1D4E4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x56;</mtext></math>=<span>1D4E5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x57;</mtext></math>=<span>1D4E6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x58;</mtext></math>=<span>1D4E7</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x59;</mtext></math>=<span>1D4E8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x5A;</mtext></math>=<span>1D4E9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x61;</mtext></math>=<span>1D4EA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x62;</mtext></math>=<span>1D4EB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x63;</mtext></math>=<span>1D4EC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x64;</mtext></math>=<span>1D4ED</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x65;</mtext></math>=<span>1D4EE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x66;</mtext></math>=<span>1D4EF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x67;</mtext></math>=<span>1D4F0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x68;</mtext></math>=<span>1D4F1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x69;</mtext></math>=<span>1D4F2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x6A;</mtext></math>=<span>1D4F3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x6B;</mtext></math>=<span>1D4F4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x6C;</mtext></math>=<span>1D4F5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x6D;</mtext></math>=<span>1D4F6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x6E;</mtext></math>=<span>1D4F7</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x6F;</mtext></math>=<span>1D4F8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x70;</mtext></math>=<span>1D4F9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x71;</mtext></math>=<span>1D4FA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x72;</mtext></math>=<span>1D4FB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x73;</mtext></math>=<span>1D4FC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x74;</mtext></math>=<span>1D4FD</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x75;</mtext></math>=<span>1D4FE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x76;</mtext></math>=<span>1D4FF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x77;</mtext></math>=<span>1D500</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x78;</mtext></math>=<span>1D501</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x79;</mtext></math>=<span>1D502</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold-script">&#x7A;</mtext></math>=<span>1D503</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold.html
new file mode 100644
index 0000000000..db5f4755be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-bold.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant bold</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#bold-mappings">
+<link rel="match" href="mathvariant-bold-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a bold mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-bold.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x41;</mtext></math>=<span>1D400</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x42;</mtext></math>=<span>1D401</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x43;</mtext></math>=<span>1D402</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x44;</mtext></math>=<span>1D403</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x45;</mtext></math>=<span>1D404</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x46;</mtext></math>=<span>1D405</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x47;</mtext></math>=<span>1D406</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x48;</mtext></math>=<span>1D407</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x49;</mtext></math>=<span>1D408</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x4A;</mtext></math>=<span>1D409</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x4B;</mtext></math>=<span>1D40A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x4C;</mtext></math>=<span>1D40B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x4D;</mtext></math>=<span>1D40C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x4E;</mtext></math>=<span>1D40D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x4F;</mtext></math>=<span>1D40E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x50;</mtext></math>=<span>1D40F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x51;</mtext></math>=<span>1D410</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x52;</mtext></math>=<span>1D411</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x53;</mtext></math>=<span>1D412</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x54;</mtext></math>=<span>1D413</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x55;</mtext></math>=<span>1D414</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x56;</mtext></math>=<span>1D415</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x57;</mtext></math>=<span>1D416</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x58;</mtext></math>=<span>1D417</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x59;</mtext></math>=<span>1D418</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x5A;</mtext></math>=<span>1D419</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x61;</mtext></math>=<span>1D41A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x62;</mtext></math>=<span>1D41B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x63;</mtext></math>=<span>1D41C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x64;</mtext></math>=<span>1D41D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x65;</mtext></math>=<span>1D41E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x66;</mtext></math>=<span>1D41F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x67;</mtext></math>=<span>1D420</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x68;</mtext></math>=<span>1D421</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x69;</mtext></math>=<span>1D422</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x6A;</mtext></math>=<span>1D423</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x6B;</mtext></math>=<span>1D424</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x6C;</mtext></math>=<span>1D425</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x6D;</mtext></math>=<span>1D426</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x6E;</mtext></math>=<span>1D427</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x6F;</mtext></math>=<span>1D428</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x70;</mtext></math>=<span>1D429</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x71;</mtext></math>=<span>1D42A</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x72;</mtext></math>=<span>1D42B</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x73;</mtext></math>=<span>1D42C</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x74;</mtext></math>=<span>1D42D</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x75;</mtext></math>=<span>1D42E</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x76;</mtext></math>=<span>1D42F</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x77;</mtext></math>=<span>1D430</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x78;</mtext></math>=<span>1D431</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x79;</mtext></math>=<span>1D432</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x7A;</mtext></math>=<span>1D433</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x391;</mtext></math>=<span>1D6A8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x392;</mtext></math>=<span>1D6A9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x393;</mtext></math>=<span>1D6AA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x394;</mtext></math>=<span>1D6AB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x395;</mtext></math>=<span>1D6AC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x396;</mtext></math>=<span>1D6AD</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x397;</mtext></math>=<span>1D6AE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x398;</mtext></math>=<span>1D6AF</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x399;</mtext></math>=<span>1D6B0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39A;</mtext></math>=<span>1D6B1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39B;</mtext></math>=<span>1D6B2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39C;</mtext></math>=<span>1D6B3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39D;</mtext></math>=<span>1D6B4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39E;</mtext></math>=<span>1D6B5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39F;</mtext></math>=<span>1D6B6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A0;</mtext></math>=<span>1D6B7</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A1;</mtext></math>=<span>1D6B8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3F4;</mtext></math>=<span>1D6B9</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A3;</mtext></math>=<span>1D6BA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A4;</mtext></math>=<span>1D6BB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A5;</mtext></math>=<span>1D6BC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A6;</mtext></math>=<span>1D6BD</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A7;</mtext></math>=<span>1D6BE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A8;</mtext></math>=<span>1D6BF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3A9;</mtext></math>=<span>1D6C0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x2207;</mtext></math>=<span>1D6C1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B1;</mtext></math>=<span>1D6C2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B2;</mtext></math>=<span>1D6C3</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B3;</mtext></math>=<span>1D6C4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B4;</mtext></math>=<span>1D6C5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B5;</mtext></math>=<span>1D6C6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B6;</mtext></math>=<span>1D6C7</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B7;</mtext></math>=<span>1D6C8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B8;</mtext></math>=<span>1D6C9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3B9;</mtext></math>=<span>1D6CA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3BA;</mtext></math>=<span>1D6CB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3BB;</mtext></math>=<span>1D6CC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3BC;</mtext></math>=<span>1D6CD</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3BD;</mtext></math>=<span>1D6CE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3BE;</mtext></math>=<span>1D6CF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3BF;</mtext></math>=<span>1D6D0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C0;</mtext></math>=<span>1D6D1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C1;</mtext></math>=<span>1D6D2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C2;</mtext></math>=<span>1D6D3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C3;</mtext></math>=<span>1D6D4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C4;</mtext></math>=<span>1D6D5</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C5;</mtext></math>=<span>1D6D6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C6;</mtext></math>=<span>1D6D7</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C7;</mtext></math>=<span>1D6D8</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C8;</mtext></math>=<span>1D6D9</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3C9;</mtext></math>=<span>1D6DA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x2202;</mtext></math>=<span>1D6DB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3F5;</mtext></math>=<span>1D6DC</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3D1;</mtext></math>=<span>1D6DD</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3F0;</mtext></math>=<span>1D6DE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3D5;</mtext></math>=<span>1D6DF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3F1;</mtext></math>=<span>1D6E0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3D6;</mtext></math>=<span>1D6E1</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3DC;</mtext></math>=<span>1D7CA</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x3DD;</mtext></math>=<span>1D7CB</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x30;</mtext></math>=<span>1D7CE</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x31;</mtext></math>=<span>1D7CF</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x32;</mtext></math>=<span>1D7D0</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x33;</mtext></math>=<span>1D7D1</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x34;</mtext></math>=<span>1D7D2</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x35;</mtext></math>=<span>1D7D3</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x36;</mtext></math>=<span>1D7D4</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x37;</mtext></math>=<span>1D7D5</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x38;</mtext></math>=<span>1D7D6</span></span>
+ <span><math class="testfont"><mtext mathvariant="bold">&#x39;</mtext></math>=<span>1D7D7</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity-ref.html
new file mode 100644
index 0000000000..28d9acc1f4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity-ref.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant case sensitivity</title>
+<style>
+ @font-face {
+ font-family: mathvariant-bold-fraktur;
+ src: url("/fonts/math/mathvariant-bold-fraktur.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold;
+ src: url("/fonts/math/mathvariant-bold.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold-italic;
+ src: url("/fonts/math/mathvariant-bold-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold-sans-serif;
+ src: url("/fonts/math/mathvariant-bold-sans-serif.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold-script;
+ src: url("/fonts/math/mathvariant-bold-script.woff");
+ }
+ @font-face {
+ font-family: mathvariant-double-struck;
+ src: url("/fonts/math/mathvariant-double-struck.woff");
+ }
+ @font-face {
+ font-family: mathvariant-fraktur;
+ src: url("/fonts/math/mathvariant-fraktur.woff");
+ }
+ @font-face {
+ font-family: mathvariant-initial;
+ src: url("/fonts/math/mathvariant-initial.woff");
+ }
+ @font-face {
+ font-family: mathvariant-italic;
+ src: url("/fonts/math/mathvariant-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-looped;
+ src: url("/fonts/math/mathvariant-looped.woff");
+ }
+ @font-face {
+ font-family: mathvariant-monospace;
+ src: url("/fonts/math/mathvariant-monospace.woff");
+ }
+ @font-face {
+ font-family: mathvariant-sans-serif-bold-italic;
+ src: url("/fonts/math/mathvariant-sans-serif-bold-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-sans-serif;
+ src: url("/fonts/math/mathvariant-sans-serif.woff");
+ }
+ @font-face {
+ font-family: mathvariant-sans-serif-italic;
+ src: url("/fonts/math/mathvariant-sans-serif-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-script;
+ src: url("/fonts/math/mathvariant-script.woff");
+ }
+ @font-face {
+ font-family: mathvariant-stretched;
+ src: url("/fonts/math/mathvariant-stretched.woff");
+ }
+ @font-face {
+ font-family: mathvariant-tailed;
+ src: url("/fonts/math/mathvariant-tailed.woff");
+ }
+</style>
+<body>
+ <p>
+ <math style="font-family: mathvariant-bold-fraktur">
+ <mtext>&#x1D56C;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold">
+ <mtext>&#x1D400;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold-italic">
+ <mtext>&#x1D468;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold-sans-serif">
+ <mtext>&#x1D5D4;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold-script">
+ <mtext>&#x1D4D0;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-double-struck">
+ <mtext>&#x1D538;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-fraktur">
+ <mtext>&#x1D504;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-initial">
+ <mtext>&#x1EE30;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-italic">
+ <mtext>&#x1D434;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-looped">
+ <mtext>&#x1EE90;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-monospace">
+ <mtext>&#x1D670;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-sans-serif-bold-italic">
+ <mtext>&#x1D63C;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-sans-serif">
+ <mtext>&#x1D5A0;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-sans-serif-italic">
+ <mtext>&#x1D608;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-script">
+ <mtext>&#x1D49C;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-stretched">
+ <mtext>&#x1EE70;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-tailed">
+ <mtext>&#x1EE52;</mtext>
+ </math>
+ </p>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity.html
new file mode 100644
index 0000000000..21c8300a7a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-case-sensitivity.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant case sensitivity</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="match" href="mathvariant-case-sensitivity-ref.html"/>
+<meta name="assert" content="Verify that mathvariant value is case insensitive">
+<style>
+ @font-face {
+ font-family: mathvariant-bold-fraktur;
+ src: url("/fonts/math/mathvariant-bold-fraktur.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold;
+ src: url("/fonts/math/mathvariant-bold.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold-italic;
+ src: url("/fonts/math/mathvariant-bold-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold-sans-serif;
+ src: url("/fonts/math/mathvariant-bold-sans-serif.woff");
+ }
+ @font-face {
+ font-family: mathvariant-bold-script;
+ src: url("/fonts/math/mathvariant-bold-script.woff");
+ }
+ @font-face {
+ font-family: mathvariant-double-struck;
+ src: url("/fonts/math/mathvariant-double-struck.woff");
+ }
+ @font-face {
+ font-family: mathvariant-fraktur;
+ src: url("/fonts/math/mathvariant-fraktur.woff");
+ }
+ @font-face {
+ font-family: mathvariant-initial;
+ src: url("/fonts/math/mathvariant-initial.woff");
+ }
+ @font-face {
+ font-family: mathvariant-italic;
+ src: url("/fonts/math/mathvariant-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-looped;
+ src: url("/fonts/math/mathvariant-looped.woff");
+ }
+ @font-face {
+ font-family: mathvariant-monospace;
+ src: url("/fonts/math/mathvariant-monospace.woff");
+ }
+ @font-face {
+ font-family: mathvariant-sans-serif-bold-italic;
+ src: url("/fonts/math/mathvariant-sans-serif-bold-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-sans-serif;
+ src: url("/fonts/math/mathvariant-sans-serif.woff");
+ }
+ @font-face {
+ font-family: mathvariant-sans-serif-italic;
+ src: url("/fonts/math/mathvariant-sans-serif-italic.woff");
+ }
+ @font-face {
+ font-family: mathvariant-script;
+ src: url("/fonts/math/mathvariant-script.woff");
+ }
+ @font-face {
+ font-family: mathvariant-stretched;
+ src: url("/fonts/math/mathvariant-stretched.woff");
+ }
+ @font-face {
+ font-family: mathvariant-tailed;
+ src: url("/fonts/math/mathvariant-tailed.woff");
+ }
+</style>
+<body>
+ <p>
+ <math style="font-family: mathvariant-bold-fraktur">
+ <mtext mathvariant="BoLd-fRaKtUr">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold">
+ <mtext mathvariant="BoLd">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold-italic">
+ <mtext mathvariant="BoLd-iTaLiC">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold-sans-serif">
+ <mtext mathvariant="BoLd-sAnS-SeRiF">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-bold-script">
+ <mtext mathvariant="BoLd-sCrIpT">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-double-struck">
+ <mtext mathvariant="DoUbLe-sTrUcK">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-fraktur">
+ <mtext mathvariant="FrAkTuR">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-initial">
+ <mtext mathvariant="InItIaL">&#x641;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-italic">
+ <mtext mathvariant="ItAlIc">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-looped">
+ <mtext mathvariant="LoOpEd">&#x641;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-monospace">
+ <mtext mathvariant="MoNoSpAcE">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-sans-serif-bold-italic">
+ <mtext mathvariant="SaNs-sErIf-bOlD-ItAlIc">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-sans-serif">
+ <mtext mathvariant="SaNs-sErIf">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-sans-serif-italic">
+ <mtext mathvariant="SaNs-sErIf-iTaLiC">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-script">
+ <mtext mathvariant="ScRiPt">&#x41;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-stretched">
+ <mtext mathvariant="StReTcHeD">&#x641;</mtext>
+ </math>
+ </p>
+ <p>
+ <math style="font-family: mathvariant-tailed">
+ <mtext mathvariant="TaIlEd">&#x642;</mtext>
+ </math>
+ </p>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight-ref.html
new file mode 100644
index 0000000000..817723a62d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>mathvariant="double-struck" and font-style/font-weight (reference)</title>
+ <style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-double-struck.woff");
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 32px;
+ }
+ .italic { font-style: italic; }
+ .bold { font-weight: bold; }
+ </style>
+ </head>
+ <body>
+ <p>Test passes if you see three lines with text <span class="testfont">&#x1EEA1;</span> respectively rendered with italic, bold and bold-italic style:</p>
+ <p><math class="testfont"><mtext class="italic">&#x1EEA1;</mtext></math></p>
+ <p><math class="testfont"><mtext class="bold">&#x1EEA1;</mtext></math></p>
+ <p><math class="testfont"><mtext class="bold italic">&#x1EEA1;</mtext></math></p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight.html
new file mode 100644
index 0000000000..2e283e1d70
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-font-style-font-weight.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>mathvariant="double-struck" and font-style/font-weight</title>
+ <link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+ <link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1789081">
+ <link rel="match" href="mathvariant-double-struck-font-style-font-weight-ref.html"/>
+ <meta name="assert" content="Verify that mathvariant='double-struck' don't reset the font-style/font-weight properties.">
+ <style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-double-struck.woff");
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 32px;
+ }
+ .italic { font-style: italic; }
+ .bold { font-weight: bold; }
+ </style>
+ </head>
+ <body>
+ <p>Test passes if you see three lines with text <span class="testfont">&#x1EEA1;</span> respectively rendered with italic, bold and bold-italic style:</p>
+ <p><math class="testfont"><mtext mathvariant="double-struck" class="italic">&#x628;</mtext></math></p>
+ <p><math class="testfont"><mtext mathvariant="double-struck" class="bold">&#x628;</mtext></math></p>
+ <p><math class="testfont"><mtext mathvariant="double-struck" class="bold italic">&#x628;</mtext></math></p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-ref.html
new file mode 100644
index 0000000000..ebdb7a15b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck-ref.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant double-struck (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-double-struck.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D538;</mtext></math>=<span>1D538</span></span>
+ <span><math class="testfont"><mtext>&#x1D539;</mtext></math>=<span>1D539</span></span>
+ <span><math class="testfont"><mtext>&#x2102;</mtext></math>=<span>02102</span></span>
+ <span><math class="testfont"><mtext>&#x1D53B;</mtext></math>=<span>1D53B</span></span>
+ <span><math class="testfont"><mtext>&#x1D53C;</mtext></math>=<span>1D53C</span></span>
+ <span><math class="testfont"><mtext>&#x1D53D;</mtext></math>=<span>1D53D</span></span>
+ <span><math class="testfont"><mtext>&#x1D53E;</mtext></math>=<span>1D53E</span></span>
+ <span><math class="testfont"><mtext>&#x210D;</mtext></math>=<span>0210D</span></span>
+ <span><math class="testfont"><mtext>&#x1D540;</mtext></math>=<span>1D540</span></span>
+ <span><math class="testfont"><mtext>&#x1D541;</mtext></math>=<span>1D541</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D542;</mtext></math>=<span>1D542</span></span>
+ <span><math class="testfont"><mtext>&#x1D543;</mtext></math>=<span>1D543</span></span>
+ <span><math class="testfont"><mtext>&#x1D544;</mtext></math>=<span>1D544</span></span>
+ <span><math class="testfont"><mtext>&#x2115;</mtext></math>=<span>02115</span></span>
+ <span><math class="testfont"><mtext>&#x1D546;</mtext></math>=<span>1D546</span></span>
+ <span><math class="testfont"><mtext>&#x2119;</mtext></math>=<span>02119</span></span>
+ <span><math class="testfont"><mtext>&#x211A;</mtext></math>=<span>0211A</span></span>
+ <span><math class="testfont"><mtext>&#x211D;</mtext></math>=<span>0211D</span></span>
+ <span><math class="testfont"><mtext>&#x1D54A;</mtext></math>=<span>1D54A</span></span>
+ <span><math class="testfont"><mtext>&#x1D54B;</mtext></math>=<span>1D54B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D54C;</mtext></math>=<span>1D54C</span></span>
+ <span><math class="testfont"><mtext>&#x1D54D;</mtext></math>=<span>1D54D</span></span>
+ <span><math class="testfont"><mtext>&#x1D54E;</mtext></math>=<span>1D54E</span></span>
+ <span><math class="testfont"><mtext>&#x1D54F;</mtext></math>=<span>1D54F</span></span>
+ <span><math class="testfont"><mtext>&#x1D550;</mtext></math>=<span>1D550</span></span>
+ <span><math class="testfont"><mtext>&#x2124;</mtext></math>=<span>02124</span></span>
+ <span><math class="testfont"><mtext>&#x1D552;</mtext></math>=<span>1D552</span></span>
+ <span><math class="testfont"><mtext>&#x1D553;</mtext></math>=<span>1D553</span></span>
+ <span><math class="testfont"><mtext>&#x1D554;</mtext></math>=<span>1D554</span></span>
+ <span><math class="testfont"><mtext>&#x1D555;</mtext></math>=<span>1D555</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D556;</mtext></math>=<span>1D556</span></span>
+ <span><math class="testfont"><mtext>&#x1D557;</mtext></math>=<span>1D557</span></span>
+ <span><math class="testfont"><mtext>&#x1D558;</mtext></math>=<span>1D558</span></span>
+ <span><math class="testfont"><mtext>&#x1D559;</mtext></math>=<span>1D559</span></span>
+ <span><math class="testfont"><mtext>&#x1D55A;</mtext></math>=<span>1D55A</span></span>
+ <span><math class="testfont"><mtext>&#x1D55B;</mtext></math>=<span>1D55B</span></span>
+ <span><math class="testfont"><mtext>&#x1D55C;</mtext></math>=<span>1D55C</span></span>
+ <span><math class="testfont"><mtext>&#x1D55D;</mtext></math>=<span>1D55D</span></span>
+ <span><math class="testfont"><mtext>&#x1D55E;</mtext></math>=<span>1D55E</span></span>
+ <span><math class="testfont"><mtext>&#x1D55F;</mtext></math>=<span>1D55F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D560;</mtext></math>=<span>1D560</span></span>
+ <span><math class="testfont"><mtext>&#x1D561;</mtext></math>=<span>1D561</span></span>
+ <span><math class="testfont"><mtext>&#x1D562;</mtext></math>=<span>1D562</span></span>
+ <span><math class="testfont"><mtext>&#x1D563;</mtext></math>=<span>1D563</span></span>
+ <span><math class="testfont"><mtext>&#x1D564;</mtext></math>=<span>1D564</span></span>
+ <span><math class="testfont"><mtext>&#x1D565;</mtext></math>=<span>1D565</span></span>
+ <span><math class="testfont"><mtext>&#x1D566;</mtext></math>=<span>1D566</span></span>
+ <span><math class="testfont"><mtext>&#x1D567;</mtext></math>=<span>1D567</span></span>
+ <span><math class="testfont"><mtext>&#x1D568;</mtext></math>=<span>1D568</span></span>
+ <span><math class="testfont"><mtext>&#x1D569;</mtext></math>=<span>1D569</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D56A;</mtext></math>=<span>1D56A</span></span>
+ <span><math class="testfont"><mtext>&#x1D56B;</mtext></math>=<span>1D56B</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D8;</mtext></math>=<span>1D7D8</span></span>
+ <span><math class="testfont"><mtext>&#x1D7D9;</mtext></math>=<span>1D7D9</span></span>
+ <span><math class="testfont"><mtext>&#x1D7DA;</mtext></math>=<span>1D7DA</span></span>
+ <span><math class="testfont"><mtext>&#x1D7DB;</mtext></math>=<span>1D7DB</span></span>
+ <span><math class="testfont"><mtext>&#x1D7DC;</mtext></math>=<span>1D7DC</span></span>
+ <span><math class="testfont"><mtext>&#x1D7DD;</mtext></math>=<span>1D7DD</span></span>
+ <span><math class="testfont"><mtext>&#x1D7DE;</mtext></math>=<span>1D7DE</span></span>
+ <span><math class="testfont"><mtext>&#x1D7DF;</mtext></math>=<span>1D7DF</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7E0;</mtext></math>=<span>1D7E0</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E1;</mtext></math>=<span>1D7E1</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA1;</mtext></math>=<span>1EEA1</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA2;</mtext></math>=<span>1EEA2</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA3;</mtext></math>=<span>1EEA3</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA5;</mtext></math>=<span>1EEA5</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA6;</mtext></math>=<span>1EEA6</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA7;</mtext></math>=<span>1EEA7</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA8;</mtext></math>=<span>1EEA8</span></span>
+ <span><math class="testfont"><mtext>&#x1EEA9;</mtext></math>=<span>1EEA9</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EEAB;</mtext></math>=<span>1EEAB</span></span>
+ <span><math class="testfont"><mtext>&#x1EEAC;</mtext></math>=<span>1EEAC</span></span>
+ <span><math class="testfont"><mtext>&#x1EEAD;</mtext></math>=<span>1EEAD</span></span>
+ <span><math class="testfont"><mtext>&#x1EEAE;</mtext></math>=<span>1EEAE</span></span>
+ <span><math class="testfont"><mtext>&#x1EEAF;</mtext></math>=<span>1EEAF</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB0;</mtext></math>=<span>1EEB0</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB1;</mtext></math>=<span>1EEB1</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB2;</mtext></math>=<span>1EEB2</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB3;</mtext></math>=<span>1EEB3</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB4;</mtext></math>=<span>1EEB4</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EEB5;</mtext></math>=<span>1EEB5</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB6;</mtext></math>=<span>1EEB6</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB7;</mtext></math>=<span>1EEB7</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB8;</mtext></math>=<span>1EEB8</span></span>
+ <span><math class="testfont"><mtext>&#x1EEB9;</mtext></math>=<span>1EEB9</span></span>
+ <span><math class="testfont"><mtext>&#x1EEBA;</mtext></math>=<span>1EEBA</span></span>
+ <span><math class="testfont"><mtext>&#x1EEBB;</mtext></math>=<span>1EEBB</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck.html
new file mode 100644
index 0000000000..335234ac38
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-double-struck.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant double-struck</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#double-struck-mappings">
+<link rel="match" href="mathvariant-double-struck-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a double-struck mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-double-struck.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x41;</mtext></math>=<span>1D538</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x42;</mtext></math>=<span>1D539</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x43;</mtext></math>=<span>02102</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x44;</mtext></math>=<span>1D53B</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x45;</mtext></math>=<span>1D53C</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x46;</mtext></math>=<span>1D53D</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x47;</mtext></math>=<span>1D53E</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x48;</mtext></math>=<span>0210D</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x49;</mtext></math>=<span>1D540</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x4A;</mtext></math>=<span>1D541</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x4B;</mtext></math>=<span>1D542</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x4C;</mtext></math>=<span>1D543</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x4D;</mtext></math>=<span>1D544</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x4E;</mtext></math>=<span>02115</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x4F;</mtext></math>=<span>1D546</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x50;</mtext></math>=<span>02119</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x51;</mtext></math>=<span>0211A</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x52;</mtext></math>=<span>0211D</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x53;</mtext></math>=<span>1D54A</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x54;</mtext></math>=<span>1D54B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x55;</mtext></math>=<span>1D54C</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x56;</mtext></math>=<span>1D54D</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x57;</mtext></math>=<span>1D54E</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x58;</mtext></math>=<span>1D54F</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x59;</mtext></math>=<span>1D550</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x5A;</mtext></math>=<span>02124</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x61;</mtext></math>=<span>1D552</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62;</mtext></math>=<span>1D553</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x63;</mtext></math>=<span>1D554</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x64;</mtext></math>=<span>1D555</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x65;</mtext></math>=<span>1D556</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x66;</mtext></math>=<span>1D557</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x67;</mtext></math>=<span>1D558</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x68;</mtext></math>=<span>1D559</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x69;</mtext></math>=<span>1D55A</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x6A;</mtext></math>=<span>1D55B</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x6B;</mtext></math>=<span>1D55C</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x6C;</mtext></math>=<span>1D55D</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x6D;</mtext></math>=<span>1D55E</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x6E;</mtext></math>=<span>1D55F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x6F;</mtext></math>=<span>1D560</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x70;</mtext></math>=<span>1D561</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x71;</mtext></math>=<span>1D562</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x72;</mtext></math>=<span>1D563</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x73;</mtext></math>=<span>1D564</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x74;</mtext></math>=<span>1D565</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x75;</mtext></math>=<span>1D566</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x76;</mtext></math>=<span>1D567</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x77;</mtext></math>=<span>1D568</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x78;</mtext></math>=<span>1D569</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x79;</mtext></math>=<span>1D56A</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x7A;</mtext></math>=<span>1D56B</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x30;</mtext></math>=<span>1D7D8</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x31;</mtext></math>=<span>1D7D9</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x32;</mtext></math>=<span>1D7DA</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x33;</mtext></math>=<span>1D7DB</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x34;</mtext></math>=<span>1D7DC</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x35;</mtext></math>=<span>1D7DD</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x36;</mtext></math>=<span>1D7DE</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x37;</mtext></math>=<span>1D7DF</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x38;</mtext></math>=<span>1D7E0</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x39;</mtext></math>=<span>1D7E1</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x628;</mtext></math>=<span>1EEA1</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62C;</mtext></math>=<span>1EEA2</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62F;</mtext></math>=<span>1EEA3</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x648;</mtext></math>=<span>1EEA5</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x632;</mtext></math>=<span>1EEA6</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62D;</mtext></math>=<span>1EEA7</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x637;</mtext></math>=<span>1EEA8</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x64A;</mtext></math>=<span>1EEA9</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x644;</mtext></math>=<span>1EEAB</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x645;</mtext></math>=<span>1EEAC</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x646;</mtext></math>=<span>1EEAD</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x633;</mtext></math>=<span>1EEAE</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x639;</mtext></math>=<span>1EEAF</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x641;</mtext></math>=<span>1EEB0</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x635;</mtext></math>=<span>1EEB1</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x642;</mtext></math>=<span>1EEB2</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x631;</mtext></math>=<span>1EEB3</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x634;</mtext></math>=<span>1EEB4</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62A;</mtext></math>=<span>1EEB5</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62B;</mtext></math>=<span>1EEB6</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x62E;</mtext></math>=<span>1EEB7</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x630;</mtext></math>=<span>1EEB8</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x636;</mtext></math>=<span>1EEB9</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x638;</mtext></math>=<span>1EEBA</span></span>
+ <span><math class="testfont"><mtext mathvariant="double-struck">&#x63A;</mtext></math>=<span>1EEBB</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur-ref.html
new file mode 100644
index 0000000000..3d1dd50a7d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur-ref.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant fraktur (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-fraktur.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D504;</mtext></math>=<span>1D504</span></span>
+ <span><math class="testfont"><mtext>&#x1D505;</mtext></math>=<span>1D505</span></span>
+ <span><math class="testfont"><mtext>&#x212D;</mtext></math>=<span>0212D</span></span>
+ <span><math class="testfont"><mtext>&#x1D507;</mtext></math>=<span>1D507</span></span>
+ <span><math class="testfont"><mtext>&#x1D508;</mtext></math>=<span>1D508</span></span>
+ <span><math class="testfont"><mtext>&#x1D509;</mtext></math>=<span>1D509</span></span>
+ <span><math class="testfont"><mtext>&#x1D50A;</mtext></math>=<span>1D50A</span></span>
+ <span><math class="testfont"><mtext>&#x210C;</mtext></math>=<span>0210C</span></span>
+ <span><math class="testfont"><mtext>&#x2111;</mtext></math>=<span>02111</span></span>
+ <span><math class="testfont"><mtext>&#x1D50D;</mtext></math>=<span>1D50D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D50E;</mtext></math>=<span>1D50E</span></span>
+ <span><math class="testfont"><mtext>&#x1D50F;</mtext></math>=<span>1D50F</span></span>
+ <span><math class="testfont"><mtext>&#x1D510;</mtext></math>=<span>1D510</span></span>
+ <span><math class="testfont"><mtext>&#x1D511;</mtext></math>=<span>1D511</span></span>
+ <span><math class="testfont"><mtext>&#x1D512;</mtext></math>=<span>1D512</span></span>
+ <span><math class="testfont"><mtext>&#x1D513;</mtext></math>=<span>1D513</span></span>
+ <span><math class="testfont"><mtext>&#x1D514;</mtext></math>=<span>1D514</span></span>
+ <span><math class="testfont"><mtext>&#x211C;</mtext></math>=<span>0211C</span></span>
+ <span><math class="testfont"><mtext>&#x1D516;</mtext></math>=<span>1D516</span></span>
+ <span><math class="testfont"><mtext>&#x1D517;</mtext></math>=<span>1D517</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D518;</mtext></math>=<span>1D518</span></span>
+ <span><math class="testfont"><mtext>&#x1D519;</mtext></math>=<span>1D519</span></span>
+ <span><math class="testfont"><mtext>&#x1D51A;</mtext></math>=<span>1D51A</span></span>
+ <span><math class="testfont"><mtext>&#x1D51B;</mtext></math>=<span>1D51B</span></span>
+ <span><math class="testfont"><mtext>&#x1D51C;</mtext></math>=<span>1D51C</span></span>
+ <span><math class="testfont"><mtext>&#x2128;</mtext></math>=<span>02128</span></span>
+ <span><math class="testfont"><mtext>&#x1D51E;</mtext></math>=<span>1D51E</span></span>
+ <span><math class="testfont"><mtext>&#x1D51F;</mtext></math>=<span>1D51F</span></span>
+ <span><math class="testfont"><mtext>&#x1D520;</mtext></math>=<span>1D520</span></span>
+ <span><math class="testfont"><mtext>&#x1D521;</mtext></math>=<span>1D521</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D522;</mtext></math>=<span>1D522</span></span>
+ <span><math class="testfont"><mtext>&#x1D523;</mtext></math>=<span>1D523</span></span>
+ <span><math class="testfont"><mtext>&#x1D524;</mtext></math>=<span>1D524</span></span>
+ <span><math class="testfont"><mtext>&#x1D525;</mtext></math>=<span>1D525</span></span>
+ <span><math class="testfont"><mtext>&#x1D526;</mtext></math>=<span>1D526</span></span>
+ <span><math class="testfont"><mtext>&#x1D527;</mtext></math>=<span>1D527</span></span>
+ <span><math class="testfont"><mtext>&#x1D528;</mtext></math>=<span>1D528</span></span>
+ <span><math class="testfont"><mtext>&#x1D529;</mtext></math>=<span>1D529</span></span>
+ <span><math class="testfont"><mtext>&#x1D52A;</mtext></math>=<span>1D52A</span></span>
+ <span><math class="testfont"><mtext>&#x1D52B;</mtext></math>=<span>1D52B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D52C;</mtext></math>=<span>1D52C</span></span>
+ <span><math class="testfont"><mtext>&#x1D52D;</mtext></math>=<span>1D52D</span></span>
+ <span><math class="testfont"><mtext>&#x1D52E;</mtext></math>=<span>1D52E</span></span>
+ <span><math class="testfont"><mtext>&#x1D52F;</mtext></math>=<span>1D52F</span></span>
+ <span><math class="testfont"><mtext>&#x1D530;</mtext></math>=<span>1D530</span></span>
+ <span><math class="testfont"><mtext>&#x1D531;</mtext></math>=<span>1D531</span></span>
+ <span><math class="testfont"><mtext>&#x1D532;</mtext></math>=<span>1D532</span></span>
+ <span><math class="testfont"><mtext>&#x1D533;</mtext></math>=<span>1D533</span></span>
+ <span><math class="testfont"><mtext>&#x1D534;</mtext></math>=<span>1D534</span></span>
+ <span><math class="testfont"><mtext>&#x1D535;</mtext></math>=<span>1D535</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D536;</mtext></math>=<span>1D536</span></span>
+ <span><math class="testfont"><mtext>&#x1D537;</mtext></math>=<span>1D537</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur.html
new file mode 100644
index 0000000000..9c95cd06e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-fraktur.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant fraktur</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#fraktur-mappings">
+<link rel="match" href="mathvariant-fraktur-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a fraktur mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-fraktur.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x41;</mtext></math>=<span>1D504</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x42;</mtext></math>=<span>1D505</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x43;</mtext></math>=<span>0212D</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x44;</mtext></math>=<span>1D507</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x45;</mtext></math>=<span>1D508</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x46;</mtext></math>=<span>1D509</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x47;</mtext></math>=<span>1D50A</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x48;</mtext></math>=<span>0210C</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x49;</mtext></math>=<span>02111</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x4A;</mtext></math>=<span>1D50D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x4B;</mtext></math>=<span>1D50E</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x4C;</mtext></math>=<span>1D50F</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x4D;</mtext></math>=<span>1D510</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x4E;</mtext></math>=<span>1D511</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x4F;</mtext></math>=<span>1D512</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x50;</mtext></math>=<span>1D513</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x51;</mtext></math>=<span>1D514</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x52;</mtext></math>=<span>0211C</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x53;</mtext></math>=<span>1D516</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x54;</mtext></math>=<span>1D517</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x55;</mtext></math>=<span>1D518</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x56;</mtext></math>=<span>1D519</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x57;</mtext></math>=<span>1D51A</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x58;</mtext></math>=<span>1D51B</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x59;</mtext></math>=<span>1D51C</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x5A;</mtext></math>=<span>02128</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x61;</mtext></math>=<span>1D51E</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x62;</mtext></math>=<span>1D51F</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x63;</mtext></math>=<span>1D520</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x64;</mtext></math>=<span>1D521</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x65;</mtext></math>=<span>1D522</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x66;</mtext></math>=<span>1D523</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x67;</mtext></math>=<span>1D524</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x68;</mtext></math>=<span>1D525</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x69;</mtext></math>=<span>1D526</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x6A;</mtext></math>=<span>1D527</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x6B;</mtext></math>=<span>1D528</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x6C;</mtext></math>=<span>1D529</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x6D;</mtext></math>=<span>1D52A</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x6E;</mtext></math>=<span>1D52B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x6F;</mtext></math>=<span>1D52C</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x70;</mtext></math>=<span>1D52D</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x71;</mtext></math>=<span>1D52E</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x72;</mtext></math>=<span>1D52F</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x73;</mtext></math>=<span>1D530</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x74;</mtext></math>=<span>1D531</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x75;</mtext></math>=<span>1D532</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x76;</mtext></math>=<span>1D533</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x77;</mtext></math>=<span>1D534</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x78;</mtext></math>=<span>1D535</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x79;</mtext></math>=<span>1D536</span></span>
+ <span><math class="testfont"><mtext mathvariant="fraktur">&#x7A;</mtext></math>=<span>1D537</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial-ref.html
new file mode 100644
index 0000000000..5d5b72a1c5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant initial (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-initial.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1EE21;</mtext></math>=<span>1EE21</span></span>
+ <span><math class="testfont"><mtext>&#x1EE22;</mtext></math>=<span>1EE22</span></span>
+ <span><math class="testfont"><mtext>&#x1EE24;</mtext></math>=<span>1EE24</span></span>
+ <span><math class="testfont"><mtext>&#x1EE27;</mtext></math>=<span>1EE27</span></span>
+ <span><math class="testfont"><mtext>&#x1EE29;</mtext></math>=<span>1EE29</span></span>
+ <span><math class="testfont"><mtext>&#x1EE2A;</mtext></math>=<span>1EE2A</span></span>
+ <span><math class="testfont"><mtext>&#x1EE2B;</mtext></math>=<span>1EE2B</span></span>
+ <span><math class="testfont"><mtext>&#x1EE2C;</mtext></math>=<span>1EE2C</span></span>
+ <span><math class="testfont"><mtext>&#x1EE2D;</mtext></math>=<span>1EE2D</span></span>
+ <span><math class="testfont"><mtext>&#x1EE2E;</mtext></math>=<span>1EE2E</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EE2F;</mtext></math>=<span>1EE2F</span></span>
+ <span><math class="testfont"><mtext>&#x1EE30;</mtext></math>=<span>1EE30</span></span>
+ <span><math class="testfont"><mtext>&#x1EE31;</mtext></math>=<span>1EE31</span></span>
+ <span><math class="testfont"><mtext>&#x1EE32;</mtext></math>=<span>1EE32</span></span>
+ <span><math class="testfont"><mtext>&#x1EE34;</mtext></math>=<span>1EE34</span></span>
+ <span><math class="testfont"><mtext>&#x1EE35;</mtext></math>=<span>1EE35</span></span>
+ <span><math class="testfont"><mtext>&#x1EE36;</mtext></math>=<span>1EE36</span></span>
+ <span><math class="testfont"><mtext>&#x1EE37;</mtext></math>=<span>1EE37</span></span>
+ <span><math class="testfont"><mtext>&#x1EE39;</mtext></math>=<span>1EE39</span></span>
+ <span><math class="testfont"><mtext>&#x1EE3B;</mtext></math>=<span>1EE3B</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial.html
new file mode 100644
index 0000000000..3b0263a197
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-initial.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant initial</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#initial-mappings">
+<link rel="match" href="mathvariant-initial-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a initial mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-initial.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x628;</mtext></math>=<span>1EE21</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x62C;</mtext></math>=<span>1EE22</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x647;</mtext></math>=<span>1EE24</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x62D;</mtext></math>=<span>1EE27</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x64A;</mtext></math>=<span>1EE29</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x643;</mtext></math>=<span>1EE2A</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x644;</mtext></math>=<span>1EE2B</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x645;</mtext></math>=<span>1EE2C</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x646;</mtext></math>=<span>1EE2D</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x633;</mtext></math>=<span>1EE2E</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x639;</mtext></math>=<span>1EE2F</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x641;</mtext></math>=<span>1EE30</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x635;</mtext></math>=<span>1EE31</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x642;</mtext></math>=<span>1EE32</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x634;</mtext></math>=<span>1EE34</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x62A;</mtext></math>=<span>1EE35</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x62B;</mtext></math>=<span>1EE36</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x62E;</mtext></math>=<span>1EE37</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x636;</mtext></math>=<span>1EE39</span></span>
+ <span><math class="testfont"><mtext mathvariant="initial">&#x63A;</mtext></math>=<span>1EE3B</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic-ref.html
new file mode 100644
index 0000000000..e2078c2948
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic-ref.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant italic (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D434;</mtext></math>=<span>1D434</span></span>
+ <span><math class="testfont"><mtext>&#x1D435;</mtext></math>=<span>1D435</span></span>
+ <span><math class="testfont"><mtext>&#x1D436;</mtext></math>=<span>1D436</span></span>
+ <span><math class="testfont"><mtext>&#x1D437;</mtext></math>=<span>1D437</span></span>
+ <span><math class="testfont"><mtext>&#x1D438;</mtext></math>=<span>1D438</span></span>
+ <span><math class="testfont"><mtext>&#x1D439;</mtext></math>=<span>1D439</span></span>
+ <span><math class="testfont"><mtext>&#x1D43A;</mtext></math>=<span>1D43A</span></span>
+ <span><math class="testfont"><mtext>&#x1D43B;</mtext></math>=<span>1D43B</span></span>
+ <span><math class="testfont"><mtext>&#x1D43C;</mtext></math>=<span>1D43C</span></span>
+ <span><math class="testfont"><mtext>&#x1D43D;</mtext></math>=<span>1D43D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D43E;</mtext></math>=<span>1D43E</span></span>
+ <span><math class="testfont"><mtext>&#x1D43F;</mtext></math>=<span>1D43F</span></span>
+ <span><math class="testfont"><mtext>&#x1D440;</mtext></math>=<span>1D440</span></span>
+ <span><math class="testfont"><mtext>&#x1D441;</mtext></math>=<span>1D441</span></span>
+ <span><math class="testfont"><mtext>&#x1D442;</mtext></math>=<span>1D442</span></span>
+ <span><math class="testfont"><mtext>&#x1D443;</mtext></math>=<span>1D443</span></span>
+ <span><math class="testfont"><mtext>&#x1D444;</mtext></math>=<span>1D444</span></span>
+ <span><math class="testfont"><mtext>&#x1D445;</mtext></math>=<span>1D445</span></span>
+ <span><math class="testfont"><mtext>&#x1D446;</mtext></math>=<span>1D446</span></span>
+ <span><math class="testfont"><mtext>&#x1D447;</mtext></math>=<span>1D447</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D448;</mtext></math>=<span>1D448</span></span>
+ <span><math class="testfont"><mtext>&#x1D449;</mtext></math>=<span>1D449</span></span>
+ <span><math class="testfont"><mtext>&#x1D44A;</mtext></math>=<span>1D44A</span></span>
+ <span><math class="testfont"><mtext>&#x1D44B;</mtext></math>=<span>1D44B</span></span>
+ <span><math class="testfont"><mtext>&#x1D44C;</mtext></math>=<span>1D44C</span></span>
+ <span><math class="testfont"><mtext>&#x1D44D;</mtext></math>=<span>1D44D</span></span>
+ <span><math class="testfont"><mtext>&#x1D44E;</mtext></math>=<span>1D44E</span></span>
+ <span><math class="testfont"><mtext>&#x1D44F;</mtext></math>=<span>1D44F</span></span>
+ <span><math class="testfont"><mtext>&#x1D450;</mtext></math>=<span>1D450</span></span>
+ <span><math class="testfont"><mtext>&#x1D451;</mtext></math>=<span>1D451</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D452;</mtext></math>=<span>1D452</span></span>
+ <span><math class="testfont"><mtext>&#x1D453;</mtext></math>=<span>1D453</span></span>
+ <span><math class="testfont"><mtext>&#x1D454;</mtext></math>=<span>1D454</span></span>
+ <span><math class="testfont"><mtext>&#x210E;</mtext></math>=<span>0210E</span></span>
+ <span><math class="testfont"><mtext>&#x1D456;</mtext></math>=<span>1D456</span></span>
+ <span><math class="testfont"><mtext>&#x1D457;</mtext></math>=<span>1D457</span></span>
+ <span><math class="testfont"><mtext>&#x1D458;</mtext></math>=<span>1D458</span></span>
+ <span><math class="testfont"><mtext>&#x1D459;</mtext></math>=<span>1D459</span></span>
+ <span><math class="testfont"><mtext>&#x1D45A;</mtext></math>=<span>1D45A</span></span>
+ <span><math class="testfont"><mtext>&#x1D45B;</mtext></math>=<span>1D45B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D45C;</mtext></math>=<span>1D45C</span></span>
+ <span><math class="testfont"><mtext>&#x1D45D;</mtext></math>=<span>1D45D</span></span>
+ <span><math class="testfont"><mtext>&#x1D45E;</mtext></math>=<span>1D45E</span></span>
+ <span><math class="testfont"><mtext>&#x1D45F;</mtext></math>=<span>1D45F</span></span>
+ <span><math class="testfont"><mtext>&#x1D460;</mtext></math>=<span>1D460</span></span>
+ <span><math class="testfont"><mtext>&#x1D461;</mtext></math>=<span>1D461</span></span>
+ <span><math class="testfont"><mtext>&#x1D462;</mtext></math>=<span>1D462</span></span>
+ <span><math class="testfont"><mtext>&#x1D463;</mtext></math>=<span>1D463</span></span>
+ <span><math class="testfont"><mtext>&#x1D464;</mtext></math>=<span>1D464</span></span>
+ <span><math class="testfont"><mtext>&#x1D465;</mtext></math>=<span>1D465</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D466;</mtext></math>=<span>1D466</span></span>
+ <span><math class="testfont"><mtext>&#x1D467;</mtext></math>=<span>1D467</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A4;</mtext></math>=<span>1D6A4</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A5;</mtext></math>=<span>1D6A5</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E2;</mtext></math>=<span>1D6E2</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E3;</mtext></math>=<span>1D6E3</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E4;</mtext></math>=<span>1D6E4</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E5;</mtext></math>=<span>1D6E5</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E6;</mtext></math>=<span>1D6E6</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E7;</mtext></math>=<span>1D6E7</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6E8;</mtext></math>=<span>1D6E8</span></span>
+ <span><math class="testfont"><mtext>&#x1D6E9;</mtext></math>=<span>1D6E9</span></span>
+ <span><math class="testfont"><mtext>&#x1D6EA;</mtext></math>=<span>1D6EA</span></span>
+ <span><math class="testfont"><mtext>&#x1D6EB;</mtext></math>=<span>1D6EB</span></span>
+ <span><math class="testfont"><mtext>&#x1D6EC;</mtext></math>=<span>1D6EC</span></span>
+ <span><math class="testfont"><mtext>&#x1D6ED;</mtext></math>=<span>1D6ED</span></span>
+ <span><math class="testfont"><mtext>&#x1D6EE;</mtext></math>=<span>1D6EE</span></span>
+ <span><math class="testfont"><mtext>&#x1D6EF;</mtext></math>=<span>1D6EF</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F0;</mtext></math>=<span>1D6F0</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F1;</mtext></math>=<span>1D6F1</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6F2;</mtext></math>=<span>1D6F2</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F3;</mtext></math>=<span>1D6F3</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F4;</mtext></math>=<span>1D6F4</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F5;</mtext></math>=<span>1D6F5</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F6;</mtext></math>=<span>1D6F6</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F7;</mtext></math>=<span>1D6F7</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F8;</mtext></math>=<span>1D6F8</span></span>
+ <span><math class="testfont"><mtext>&#x1D6F9;</mtext></math>=<span>1D6F9</span></span>
+ <span><math class="testfont"><mtext>&#x1D6FA;</mtext></math>=<span>1D6FA</span></span>
+ <span><math class="testfont"><mtext>&#x1D6FB;</mtext></math>=<span>1D6FB</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6FC;</mtext></math>=<span>1D6FC</span></span>
+ <span><math class="testfont"><mtext>&#x1D6FD;</mtext></math>=<span>1D6FD</span></span>
+ <span><math class="testfont"><mtext>&#x1D6FE;</mtext></math>=<span>1D6FE</span></span>
+ <span><math class="testfont"><mtext>&#x1D6FF;</mtext></math>=<span>1D6FF</span></span>
+ <span><math class="testfont"><mtext>&#x1D700;</mtext></math>=<span>1D700</span></span>
+ <span><math class="testfont"><mtext>&#x1D701;</mtext></math>=<span>1D701</span></span>
+ <span><math class="testfont"><mtext>&#x1D702;</mtext></math>=<span>1D702</span></span>
+ <span><math class="testfont"><mtext>&#x1D703;</mtext></math>=<span>1D703</span></span>
+ <span><math class="testfont"><mtext>&#x1D704;</mtext></math>=<span>1D704</span></span>
+ <span><math class="testfont"><mtext>&#x1D705;</mtext></math>=<span>1D705</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D706;</mtext></math>=<span>1D706</span></span>
+ <span><math class="testfont"><mtext>&#x1D707;</mtext></math>=<span>1D707</span></span>
+ <span><math class="testfont"><mtext>&#x1D708;</mtext></math>=<span>1D708</span></span>
+ <span><math class="testfont"><mtext>&#x1D709;</mtext></math>=<span>1D709</span></span>
+ <span><math class="testfont"><mtext>&#x1D70A;</mtext></math>=<span>1D70A</span></span>
+ <span><math class="testfont"><mtext>&#x1D70B;</mtext></math>=<span>1D70B</span></span>
+ <span><math class="testfont"><mtext>&#x1D70C;</mtext></math>=<span>1D70C</span></span>
+ <span><math class="testfont"><mtext>&#x1D70D;</mtext></math>=<span>1D70D</span></span>
+ <span><math class="testfont"><mtext>&#x1D70E;</mtext></math>=<span>1D70E</span></span>
+ <span><math class="testfont"><mtext>&#x1D70F;</mtext></math>=<span>1D70F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D710;</mtext></math>=<span>1D710</span></span>
+ <span><math class="testfont"><mtext>&#x1D711;</mtext></math>=<span>1D711</span></span>
+ <span><math class="testfont"><mtext>&#x1D712;</mtext></math>=<span>1D712</span></span>
+ <span><math class="testfont"><mtext>&#x1D713;</mtext></math>=<span>1D713</span></span>
+ <span><math class="testfont"><mtext>&#x1D714;</mtext></math>=<span>1D714</span></span>
+ <span><math class="testfont"><mtext>&#x1D715;</mtext></math>=<span>1D715</span></span>
+ <span><math class="testfont"><mtext>&#x1D716;</mtext></math>=<span>1D716</span></span>
+ <span><math class="testfont"><mtext>&#x1D717;</mtext></math>=<span>1D717</span></span>
+ <span><math class="testfont"><mtext>&#x1D718;</mtext></math>=<span>1D718</span></span>
+ <span><math class="testfont"><mtext>&#x1D719;</mtext></math>=<span>1D719</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D71A;</mtext></math>=<span>1D71A</span></span>
+ <span><math class="testfont"><mtext>&#x1D71B;</mtext></math>=<span>1D71B</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic.html
new file mode 100644
index 0000000000..1e6aa6512d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-italic.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant italic</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#italic-mappings">
+<link rel="match" href="mathvariant-italic-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a italic mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x41;</mtext></math>=<span>1D434</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x42;</mtext></math>=<span>1D435</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x43;</mtext></math>=<span>1D436</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x44;</mtext></math>=<span>1D437</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x45;</mtext></math>=<span>1D438</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x46;</mtext></math>=<span>1D439</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x47;</mtext></math>=<span>1D43A</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x48;</mtext></math>=<span>1D43B</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x49;</mtext></math>=<span>1D43C</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x4A;</mtext></math>=<span>1D43D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x4B;</mtext></math>=<span>1D43E</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x4C;</mtext></math>=<span>1D43F</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x4D;</mtext></math>=<span>1D440</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x4E;</mtext></math>=<span>1D441</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x4F;</mtext></math>=<span>1D442</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x50;</mtext></math>=<span>1D443</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x51;</mtext></math>=<span>1D444</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x52;</mtext></math>=<span>1D445</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x53;</mtext></math>=<span>1D446</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x54;</mtext></math>=<span>1D447</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x55;</mtext></math>=<span>1D448</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x56;</mtext></math>=<span>1D449</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x57;</mtext></math>=<span>1D44A</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x58;</mtext></math>=<span>1D44B</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x59;</mtext></math>=<span>1D44C</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x5A;</mtext></math>=<span>1D44D</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x61;</mtext></math>=<span>1D44E</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x62;</mtext></math>=<span>1D44F</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x63;</mtext></math>=<span>1D450</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x64;</mtext></math>=<span>1D451</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x65;</mtext></math>=<span>1D452</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x66;</mtext></math>=<span>1D453</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x67;</mtext></math>=<span>1D454</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x68;</mtext></math>=<span>0210E</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x69;</mtext></math>=<span>1D456</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x6A;</mtext></math>=<span>1D457</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x6B;</mtext></math>=<span>1D458</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x6C;</mtext></math>=<span>1D459</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x6D;</mtext></math>=<span>1D45A</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x6E;</mtext></math>=<span>1D45B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x6F;</mtext></math>=<span>1D45C</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x70;</mtext></math>=<span>1D45D</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x71;</mtext></math>=<span>1D45E</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x72;</mtext></math>=<span>1D45F</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x73;</mtext></math>=<span>1D460</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x74;</mtext></math>=<span>1D461</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x75;</mtext></math>=<span>1D462</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x76;</mtext></math>=<span>1D463</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x77;</mtext></math>=<span>1D464</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x78;</mtext></math>=<span>1D465</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x79;</mtext></math>=<span>1D466</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x7A;</mtext></math>=<span>1D467</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x131;</mtext></math>=<span>1D6A4</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x237;</mtext></math>=<span>1D6A5</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x391;</mtext></math>=<span>1D6E2</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x392;</mtext></math>=<span>1D6E3</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x393;</mtext></math>=<span>1D6E4</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x394;</mtext></math>=<span>1D6E5</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x395;</mtext></math>=<span>1D6E6</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x396;</mtext></math>=<span>1D6E7</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x397;</mtext></math>=<span>1D6E8</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x398;</mtext></math>=<span>1D6E9</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x399;</mtext></math>=<span>1D6EA</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x39A;</mtext></math>=<span>1D6EB</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x39B;</mtext></math>=<span>1D6EC</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x39C;</mtext></math>=<span>1D6ED</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x39D;</mtext></math>=<span>1D6EE</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x39E;</mtext></math>=<span>1D6EF</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x39F;</mtext></math>=<span>1D6F0</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A0;</mtext></math>=<span>1D6F1</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A1;</mtext></math>=<span>1D6F2</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3F4;</mtext></math>=<span>1D6F3</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A3;</mtext></math>=<span>1D6F4</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A4;</mtext></math>=<span>1D6F5</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A5;</mtext></math>=<span>1D6F6</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A6;</mtext></math>=<span>1D6F7</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A7;</mtext></math>=<span>1D6F8</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A8;</mtext></math>=<span>1D6F9</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3A9;</mtext></math>=<span>1D6FA</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x2207;</mtext></math>=<span>1D6FB</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B1;</mtext></math>=<span>1D6FC</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B2;</mtext></math>=<span>1D6FD</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B3;</mtext></math>=<span>1D6FE</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B4;</mtext></math>=<span>1D6FF</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B5;</mtext></math>=<span>1D700</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B6;</mtext></math>=<span>1D701</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B7;</mtext></math>=<span>1D702</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B8;</mtext></math>=<span>1D703</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3B9;</mtext></math>=<span>1D704</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3BA;</mtext></math>=<span>1D705</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3BB;</mtext></math>=<span>1D706</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3BC;</mtext></math>=<span>1D707</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3BD;</mtext></math>=<span>1D708</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3BE;</mtext></math>=<span>1D709</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3BF;</mtext></math>=<span>1D70A</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C0;</mtext></math>=<span>1D70B</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C1;</mtext></math>=<span>1D70C</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C2;</mtext></math>=<span>1D70D</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C3;</mtext></math>=<span>1D70E</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C4;</mtext></math>=<span>1D70F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C5;</mtext></math>=<span>1D710</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C6;</mtext></math>=<span>1D711</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C7;</mtext></math>=<span>1D712</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C8;</mtext></math>=<span>1D713</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3C9;</mtext></math>=<span>1D714</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x2202;</mtext></math>=<span>1D715</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3F5;</mtext></math>=<span>1D716</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3D1;</mtext></math>=<span>1D717</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3F0;</mtext></math>=<span>1D718</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3D5;</mtext></math>=<span>1D719</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3F1;</mtext></math>=<span>1D71A</span></span>
+ <span><math class="testfont"><mtext mathvariant="italic">&#x3D6;</mtext></math>=<span>1D71B</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped-ref.html
new file mode 100644
index 0000000000..f00e5d87bb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped-ref.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant looped (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-looped.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1EE80;</mtext></math>=<span>1EE80</span></span>
+ <span><math class="testfont"><mtext>&#x1EE81;</mtext></math>=<span>1EE81</span></span>
+ <span><math class="testfont"><mtext>&#x1EE82;</mtext></math>=<span>1EE82</span></span>
+ <span><math class="testfont"><mtext>&#x1EE83;</mtext></math>=<span>1EE83</span></span>
+ <span><math class="testfont"><mtext>&#x1EE84;</mtext></math>=<span>1EE84</span></span>
+ <span><math class="testfont"><mtext>&#x1EE85;</mtext></math>=<span>1EE85</span></span>
+ <span><math class="testfont"><mtext>&#x1EE86;</mtext></math>=<span>1EE86</span></span>
+ <span><math class="testfont"><mtext>&#x1EE87;</mtext></math>=<span>1EE87</span></span>
+ <span><math class="testfont"><mtext>&#x1EE88;</mtext></math>=<span>1EE88</span></span>
+ <span><math class="testfont"><mtext>&#x1EE89;</mtext></math>=<span>1EE89</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EE8B;</mtext></math>=<span>1EE8B</span></span>
+ <span><math class="testfont"><mtext>&#x1EE8C;</mtext></math>=<span>1EE8C</span></span>
+ <span><math class="testfont"><mtext>&#x1EE8D;</mtext></math>=<span>1EE8D</span></span>
+ <span><math class="testfont"><mtext>&#x1EE8E;</mtext></math>=<span>1EE8E</span></span>
+ <span><math class="testfont"><mtext>&#x1EE8F;</mtext></math>=<span>1EE8F</span></span>
+ <span><math class="testfont"><mtext>&#x1EE90;</mtext></math>=<span>1EE90</span></span>
+ <span><math class="testfont"><mtext>&#x1EE91;</mtext></math>=<span>1EE91</span></span>
+ <span><math class="testfont"><mtext>&#x1EE92;</mtext></math>=<span>1EE92</span></span>
+ <span><math class="testfont"><mtext>&#x1EE93;</mtext></math>=<span>1EE93</span></span>
+ <span><math class="testfont"><mtext>&#x1EE94;</mtext></math>=<span>1EE94</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EE95;</mtext></math>=<span>1EE95</span></span>
+ <span><math class="testfont"><mtext>&#x1EE96;</mtext></math>=<span>1EE96</span></span>
+ <span><math class="testfont"><mtext>&#x1EE97;</mtext></math>=<span>1EE97</span></span>
+ <span><math class="testfont"><mtext>&#x1EE98;</mtext></math>=<span>1EE98</span></span>
+ <span><math class="testfont"><mtext>&#x1EE99;</mtext></math>=<span>1EE99</span></span>
+ <span><math class="testfont"><mtext>&#x1EE9A;</mtext></math>=<span>1EE9A</span></span>
+ <span><math class="testfont"><mtext>&#x1EE9B;</mtext></math>=<span>1EE9B</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped.html
new file mode 100644
index 0000000000..b208849368
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-looped.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant looped</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#looped-mappings">
+<link rel="match" href="mathvariant-looped-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a looped mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-looped.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x627;</mtext></math>=<span>1EE80</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x628;</mtext></math>=<span>1EE81</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x62C;</mtext></math>=<span>1EE82</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x62F;</mtext></math>=<span>1EE83</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x647;</mtext></math>=<span>1EE84</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x648;</mtext></math>=<span>1EE85</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x632;</mtext></math>=<span>1EE86</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x62D;</mtext></math>=<span>1EE87</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x637;</mtext></math>=<span>1EE88</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x64A;</mtext></math>=<span>1EE89</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x644;</mtext></math>=<span>1EE8B</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x645;</mtext></math>=<span>1EE8C</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x646;</mtext></math>=<span>1EE8D</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x633;</mtext></math>=<span>1EE8E</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x639;</mtext></math>=<span>1EE8F</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x641;</mtext></math>=<span>1EE90</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x635;</mtext></math>=<span>1EE91</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x642;</mtext></math>=<span>1EE92</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x631;</mtext></math>=<span>1EE93</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x634;</mtext></math>=<span>1EE94</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x62A;</mtext></math>=<span>1EE95</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x62B;</mtext></math>=<span>1EE96</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x62E;</mtext></math>=<span>1EE97</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x630;</mtext></math>=<span>1EE98</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x636;</mtext></math>=<span>1EE99</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x638;</mtext></math>=<span>1EE9A</span></span>
+ <span><math class="testfont"><mtext mathvariant="looped">&#x63A;</mtext></math>=<span>1EE9B</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace-ref.html
new file mode 100644
index 0000000000..1d4a0c0a52
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace-ref.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant monospace (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-monospace.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D670;</mtext></math>=<span>1D670</span></span>
+ <span><math class="testfont"><mtext>&#x1D671;</mtext></math>=<span>1D671</span></span>
+ <span><math class="testfont"><mtext>&#x1D672;</mtext></math>=<span>1D672</span></span>
+ <span><math class="testfont"><mtext>&#x1D673;</mtext></math>=<span>1D673</span></span>
+ <span><math class="testfont"><mtext>&#x1D674;</mtext></math>=<span>1D674</span></span>
+ <span><math class="testfont"><mtext>&#x1D675;</mtext></math>=<span>1D675</span></span>
+ <span><math class="testfont"><mtext>&#x1D676;</mtext></math>=<span>1D676</span></span>
+ <span><math class="testfont"><mtext>&#x1D677;</mtext></math>=<span>1D677</span></span>
+ <span><math class="testfont"><mtext>&#x1D678;</mtext></math>=<span>1D678</span></span>
+ <span><math class="testfont"><mtext>&#x1D679;</mtext></math>=<span>1D679</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D67A;</mtext></math>=<span>1D67A</span></span>
+ <span><math class="testfont"><mtext>&#x1D67B;</mtext></math>=<span>1D67B</span></span>
+ <span><math class="testfont"><mtext>&#x1D67C;</mtext></math>=<span>1D67C</span></span>
+ <span><math class="testfont"><mtext>&#x1D67D;</mtext></math>=<span>1D67D</span></span>
+ <span><math class="testfont"><mtext>&#x1D67E;</mtext></math>=<span>1D67E</span></span>
+ <span><math class="testfont"><mtext>&#x1D67F;</mtext></math>=<span>1D67F</span></span>
+ <span><math class="testfont"><mtext>&#x1D680;</mtext></math>=<span>1D680</span></span>
+ <span><math class="testfont"><mtext>&#x1D681;</mtext></math>=<span>1D681</span></span>
+ <span><math class="testfont"><mtext>&#x1D682;</mtext></math>=<span>1D682</span></span>
+ <span><math class="testfont"><mtext>&#x1D683;</mtext></math>=<span>1D683</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D684;</mtext></math>=<span>1D684</span></span>
+ <span><math class="testfont"><mtext>&#x1D685;</mtext></math>=<span>1D685</span></span>
+ <span><math class="testfont"><mtext>&#x1D686;</mtext></math>=<span>1D686</span></span>
+ <span><math class="testfont"><mtext>&#x1D687;</mtext></math>=<span>1D687</span></span>
+ <span><math class="testfont"><mtext>&#x1D688;</mtext></math>=<span>1D688</span></span>
+ <span><math class="testfont"><mtext>&#x1D689;</mtext></math>=<span>1D689</span></span>
+ <span><math class="testfont"><mtext>&#x1D68A;</mtext></math>=<span>1D68A</span></span>
+ <span><math class="testfont"><mtext>&#x1D68B;</mtext></math>=<span>1D68B</span></span>
+ <span><math class="testfont"><mtext>&#x1D68C;</mtext></math>=<span>1D68C</span></span>
+ <span><math class="testfont"><mtext>&#x1D68D;</mtext></math>=<span>1D68D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D68E;</mtext></math>=<span>1D68E</span></span>
+ <span><math class="testfont"><mtext>&#x1D68F;</mtext></math>=<span>1D68F</span></span>
+ <span><math class="testfont"><mtext>&#x1D690;</mtext></math>=<span>1D690</span></span>
+ <span><math class="testfont"><mtext>&#x1D691;</mtext></math>=<span>1D691</span></span>
+ <span><math class="testfont"><mtext>&#x1D692;</mtext></math>=<span>1D692</span></span>
+ <span><math class="testfont"><mtext>&#x1D693;</mtext></math>=<span>1D693</span></span>
+ <span><math class="testfont"><mtext>&#x1D694;</mtext></math>=<span>1D694</span></span>
+ <span><math class="testfont"><mtext>&#x1D695;</mtext></math>=<span>1D695</span></span>
+ <span><math class="testfont"><mtext>&#x1D696;</mtext></math>=<span>1D696</span></span>
+ <span><math class="testfont"><mtext>&#x1D697;</mtext></math>=<span>1D697</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D698;</mtext></math>=<span>1D698</span></span>
+ <span><math class="testfont"><mtext>&#x1D699;</mtext></math>=<span>1D699</span></span>
+ <span><math class="testfont"><mtext>&#x1D69A;</mtext></math>=<span>1D69A</span></span>
+ <span><math class="testfont"><mtext>&#x1D69B;</mtext></math>=<span>1D69B</span></span>
+ <span><math class="testfont"><mtext>&#x1D69C;</mtext></math>=<span>1D69C</span></span>
+ <span><math class="testfont"><mtext>&#x1D69D;</mtext></math>=<span>1D69D</span></span>
+ <span><math class="testfont"><mtext>&#x1D69E;</mtext></math>=<span>1D69E</span></span>
+ <span><math class="testfont"><mtext>&#x1D69F;</mtext></math>=<span>1D69F</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A0;</mtext></math>=<span>1D6A0</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A1;</mtext></math>=<span>1D6A1</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D6A2;</mtext></math>=<span>1D6A2</span></span>
+ <span><math class="testfont"><mtext>&#x1D6A3;</mtext></math>=<span>1D6A3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F6;</mtext></math>=<span>1D7F6</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F7;</mtext></math>=<span>1D7F7</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F8;</mtext></math>=<span>1D7F8</span></span>
+ <span><math class="testfont"><mtext>&#x1D7F9;</mtext></math>=<span>1D7F9</span></span>
+ <span><math class="testfont"><mtext>&#x1D7FA;</mtext></math>=<span>1D7FA</span></span>
+ <span><math class="testfont"><mtext>&#x1D7FB;</mtext></math>=<span>1D7FB</span></span>
+ <span><math class="testfont"><mtext>&#x1D7FC;</mtext></math>=<span>1D7FC</span></span>
+ <span><math class="testfont"><mtext>&#x1D7FD;</mtext></math>=<span>1D7FD</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7FE;</mtext></math>=<span>1D7FE</span></span>
+ <span><math class="testfont"><mtext>&#x1D7FF;</mtext></math>=<span>1D7FF</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace.html
new file mode 100644
index 0000000000..ad5541fe0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-monospace.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant monospace</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#monospace-mappings">
+<link rel="match" href="mathvariant-monospace-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a monospace mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-monospace.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x41;</mtext></math>=<span>1D670</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x42;</mtext></math>=<span>1D671</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x43;</mtext></math>=<span>1D672</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x44;</mtext></math>=<span>1D673</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x45;</mtext></math>=<span>1D674</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x46;</mtext></math>=<span>1D675</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x47;</mtext></math>=<span>1D676</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x48;</mtext></math>=<span>1D677</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x49;</mtext></math>=<span>1D678</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x4A;</mtext></math>=<span>1D679</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x4B;</mtext></math>=<span>1D67A</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x4C;</mtext></math>=<span>1D67B</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x4D;</mtext></math>=<span>1D67C</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x4E;</mtext></math>=<span>1D67D</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x4F;</mtext></math>=<span>1D67E</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x50;</mtext></math>=<span>1D67F</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x51;</mtext></math>=<span>1D680</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x52;</mtext></math>=<span>1D681</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x53;</mtext></math>=<span>1D682</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x54;</mtext></math>=<span>1D683</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x55;</mtext></math>=<span>1D684</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x56;</mtext></math>=<span>1D685</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x57;</mtext></math>=<span>1D686</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x58;</mtext></math>=<span>1D687</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x59;</mtext></math>=<span>1D688</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x5A;</mtext></math>=<span>1D689</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x61;</mtext></math>=<span>1D68A</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x62;</mtext></math>=<span>1D68B</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x63;</mtext></math>=<span>1D68C</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x64;</mtext></math>=<span>1D68D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x65;</mtext></math>=<span>1D68E</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x66;</mtext></math>=<span>1D68F</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x67;</mtext></math>=<span>1D690</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x68;</mtext></math>=<span>1D691</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x69;</mtext></math>=<span>1D692</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x6A;</mtext></math>=<span>1D693</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x6B;</mtext></math>=<span>1D694</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x6C;</mtext></math>=<span>1D695</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x6D;</mtext></math>=<span>1D696</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x6E;</mtext></math>=<span>1D697</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x6F;</mtext></math>=<span>1D698</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x70;</mtext></math>=<span>1D699</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x71;</mtext></math>=<span>1D69A</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x72;</mtext></math>=<span>1D69B</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x73;</mtext></math>=<span>1D69C</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x74;</mtext></math>=<span>1D69D</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x75;</mtext></math>=<span>1D69E</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x76;</mtext></math>=<span>1D69F</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x77;</mtext></math>=<span>1D6A0</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x78;</mtext></math>=<span>1D6A1</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x79;</mtext></math>=<span>1D6A2</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x7A;</mtext></math>=<span>1D6A3</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x30;</mtext></math>=<span>1D7F6</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x31;</mtext></math>=<span>1D7F7</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x32;</mtext></math>=<span>1D7F8</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x33;</mtext></math>=<span>1D7F9</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x34;</mtext></math>=<span>1D7FA</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x35;</mtext></math>=<span>1D7FB</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x36;</mtext></math>=<span>1D7FC</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x37;</mtext></math>=<span>1D7FD</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x38;</mtext></math>=<span>1D7FE</span></span>
+ <span><math class="testfont"><mtext mathvariant="monospace">&#x39;</mtext></math>=<span>1D7FF</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic-ref.html
new file mode 100644
index 0000000000..f918db9b48
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic-ref.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant sans-serif-bold-italic (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-sans-serif-bold-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D63C;</mtext></math>=<span>1D63C</span></span>
+ <span><math class="testfont"><mtext>&#x1D63D;</mtext></math>=<span>1D63D</span></span>
+ <span><math class="testfont"><mtext>&#x1D63E;</mtext></math>=<span>1D63E</span></span>
+ <span><math class="testfont"><mtext>&#x1D63F;</mtext></math>=<span>1D63F</span></span>
+ <span><math class="testfont"><mtext>&#x1D640;</mtext></math>=<span>1D640</span></span>
+ <span><math class="testfont"><mtext>&#x1D641;</mtext></math>=<span>1D641</span></span>
+ <span><math class="testfont"><mtext>&#x1D642;</mtext></math>=<span>1D642</span></span>
+ <span><math class="testfont"><mtext>&#x1D643;</mtext></math>=<span>1D643</span></span>
+ <span><math class="testfont"><mtext>&#x1D644;</mtext></math>=<span>1D644</span></span>
+ <span><math class="testfont"><mtext>&#x1D645;</mtext></math>=<span>1D645</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D646;</mtext></math>=<span>1D646</span></span>
+ <span><math class="testfont"><mtext>&#x1D647;</mtext></math>=<span>1D647</span></span>
+ <span><math class="testfont"><mtext>&#x1D648;</mtext></math>=<span>1D648</span></span>
+ <span><math class="testfont"><mtext>&#x1D649;</mtext></math>=<span>1D649</span></span>
+ <span><math class="testfont"><mtext>&#x1D64A;</mtext></math>=<span>1D64A</span></span>
+ <span><math class="testfont"><mtext>&#x1D64B;</mtext></math>=<span>1D64B</span></span>
+ <span><math class="testfont"><mtext>&#x1D64C;</mtext></math>=<span>1D64C</span></span>
+ <span><math class="testfont"><mtext>&#x1D64D;</mtext></math>=<span>1D64D</span></span>
+ <span><math class="testfont"><mtext>&#x1D64E;</mtext></math>=<span>1D64E</span></span>
+ <span><math class="testfont"><mtext>&#x1D64F;</mtext></math>=<span>1D64F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D650;</mtext></math>=<span>1D650</span></span>
+ <span><math class="testfont"><mtext>&#x1D651;</mtext></math>=<span>1D651</span></span>
+ <span><math class="testfont"><mtext>&#x1D652;</mtext></math>=<span>1D652</span></span>
+ <span><math class="testfont"><mtext>&#x1D653;</mtext></math>=<span>1D653</span></span>
+ <span><math class="testfont"><mtext>&#x1D654;</mtext></math>=<span>1D654</span></span>
+ <span><math class="testfont"><mtext>&#x1D655;</mtext></math>=<span>1D655</span></span>
+ <span><math class="testfont"><mtext>&#x1D656;</mtext></math>=<span>1D656</span></span>
+ <span><math class="testfont"><mtext>&#x1D657;</mtext></math>=<span>1D657</span></span>
+ <span><math class="testfont"><mtext>&#x1D658;</mtext></math>=<span>1D658</span></span>
+ <span><math class="testfont"><mtext>&#x1D659;</mtext></math>=<span>1D659</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D65A;</mtext></math>=<span>1D65A</span></span>
+ <span><math class="testfont"><mtext>&#x1D65B;</mtext></math>=<span>1D65B</span></span>
+ <span><math class="testfont"><mtext>&#x1D65C;</mtext></math>=<span>1D65C</span></span>
+ <span><math class="testfont"><mtext>&#x1D65D;</mtext></math>=<span>1D65D</span></span>
+ <span><math class="testfont"><mtext>&#x1D65E;</mtext></math>=<span>1D65E</span></span>
+ <span><math class="testfont"><mtext>&#x1D65F;</mtext></math>=<span>1D65F</span></span>
+ <span><math class="testfont"><mtext>&#x1D660;</mtext></math>=<span>1D660</span></span>
+ <span><math class="testfont"><mtext>&#x1D661;</mtext></math>=<span>1D661</span></span>
+ <span><math class="testfont"><mtext>&#x1D662;</mtext></math>=<span>1D662</span></span>
+ <span><math class="testfont"><mtext>&#x1D663;</mtext></math>=<span>1D663</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D664;</mtext></math>=<span>1D664</span></span>
+ <span><math class="testfont"><mtext>&#x1D665;</mtext></math>=<span>1D665</span></span>
+ <span><math class="testfont"><mtext>&#x1D666;</mtext></math>=<span>1D666</span></span>
+ <span><math class="testfont"><mtext>&#x1D667;</mtext></math>=<span>1D667</span></span>
+ <span><math class="testfont"><mtext>&#x1D668;</mtext></math>=<span>1D668</span></span>
+ <span><math class="testfont"><mtext>&#x1D669;</mtext></math>=<span>1D669</span></span>
+ <span><math class="testfont"><mtext>&#x1D66A;</mtext></math>=<span>1D66A</span></span>
+ <span><math class="testfont"><mtext>&#x1D66B;</mtext></math>=<span>1D66B</span></span>
+ <span><math class="testfont"><mtext>&#x1D66C;</mtext></math>=<span>1D66C</span></span>
+ <span><math class="testfont"><mtext>&#x1D66D;</mtext></math>=<span>1D66D</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D66E;</mtext></math>=<span>1D66E</span></span>
+ <span><math class="testfont"><mtext>&#x1D66F;</mtext></math>=<span>1D66F</span></span>
+ <span><math class="testfont"><mtext>&#x1D790;</mtext></math>=<span>1D790</span></span>
+ <span><math class="testfont"><mtext>&#x1D791;</mtext></math>=<span>1D791</span></span>
+ <span><math class="testfont"><mtext>&#x1D792;</mtext></math>=<span>1D792</span></span>
+ <span><math class="testfont"><mtext>&#x1D793;</mtext></math>=<span>1D793</span></span>
+ <span><math class="testfont"><mtext>&#x1D794;</mtext></math>=<span>1D794</span></span>
+ <span><math class="testfont"><mtext>&#x1D795;</mtext></math>=<span>1D795</span></span>
+ <span><math class="testfont"><mtext>&#x1D796;</mtext></math>=<span>1D796</span></span>
+ <span><math class="testfont"><mtext>&#x1D797;</mtext></math>=<span>1D797</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D798;</mtext></math>=<span>1D798</span></span>
+ <span><math class="testfont"><mtext>&#x1D799;</mtext></math>=<span>1D799</span></span>
+ <span><math class="testfont"><mtext>&#x1D79A;</mtext></math>=<span>1D79A</span></span>
+ <span><math class="testfont"><mtext>&#x1D79B;</mtext></math>=<span>1D79B</span></span>
+ <span><math class="testfont"><mtext>&#x1D79C;</mtext></math>=<span>1D79C</span></span>
+ <span><math class="testfont"><mtext>&#x1D79D;</mtext></math>=<span>1D79D</span></span>
+ <span><math class="testfont"><mtext>&#x1D79E;</mtext></math>=<span>1D79E</span></span>
+ <span><math class="testfont"><mtext>&#x1D79F;</mtext></math>=<span>1D79F</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A0;</mtext></math>=<span>1D7A0</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A1;</mtext></math>=<span>1D7A1</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7A2;</mtext></math>=<span>1D7A2</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A3;</mtext></math>=<span>1D7A3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A4;</mtext></math>=<span>1D7A4</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A5;</mtext></math>=<span>1D7A5</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A6;</mtext></math>=<span>1D7A6</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A7;</mtext></math>=<span>1D7A7</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A8;</mtext></math>=<span>1D7A8</span></span>
+ <span><math class="testfont"><mtext>&#x1D7A9;</mtext></math>=<span>1D7A9</span></span>
+ <span><math class="testfont"><mtext>&#x1D7AA;</mtext></math>=<span>1D7AA</span></span>
+ <span><math class="testfont"><mtext>&#x1D7AB;</mtext></math>=<span>1D7AB</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7AC;</mtext></math>=<span>1D7AC</span></span>
+ <span><math class="testfont"><mtext>&#x1D7AD;</mtext></math>=<span>1D7AD</span></span>
+ <span><math class="testfont"><mtext>&#x1D7AE;</mtext></math>=<span>1D7AE</span></span>
+ <span><math class="testfont"><mtext>&#x1D7AF;</mtext></math>=<span>1D7AF</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B0;</mtext></math>=<span>1D7B0</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B1;</mtext></math>=<span>1D7B1</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B2;</mtext></math>=<span>1D7B2</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B3;</mtext></math>=<span>1D7B3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B4;</mtext></math>=<span>1D7B4</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B5;</mtext></math>=<span>1D7B5</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7B6;</mtext></math>=<span>1D7B6</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B7;</mtext></math>=<span>1D7B7</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B8;</mtext></math>=<span>1D7B8</span></span>
+ <span><math class="testfont"><mtext>&#x1D7B9;</mtext></math>=<span>1D7B9</span></span>
+ <span><math class="testfont"><mtext>&#x1D7BA;</mtext></math>=<span>1D7BA</span></span>
+ <span><math class="testfont"><mtext>&#x1D7BB;</mtext></math>=<span>1D7BB</span></span>
+ <span><math class="testfont"><mtext>&#x1D7BC;</mtext></math>=<span>1D7BC</span></span>
+ <span><math class="testfont"><mtext>&#x1D7BD;</mtext></math>=<span>1D7BD</span></span>
+ <span><math class="testfont"><mtext>&#x1D7BE;</mtext></math>=<span>1D7BE</span></span>
+ <span><math class="testfont"><mtext>&#x1D7BF;</mtext></math>=<span>1D7BF</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7C0;</mtext></math>=<span>1D7C0</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C1;</mtext></math>=<span>1D7C1</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C2;</mtext></math>=<span>1D7C2</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C3;</mtext></math>=<span>1D7C3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C4;</mtext></math>=<span>1D7C4</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C5;</mtext></math>=<span>1D7C5</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C6;</mtext></math>=<span>1D7C6</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C7;</mtext></math>=<span>1D7C7</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C8;</mtext></math>=<span>1D7C8</span></span>
+ <span><math class="testfont"><mtext>&#x1D7C9;</mtext></math>=<span>1D7C9</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic.html
new file mode 100644
index 0000000000..d51afe4345
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-bold-italic.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant sans-serif-bold-italic</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#sans-serif-bold-italic-mappings">
+<link rel="match" href="mathvariant-sans-serif-bold-italic-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a sans-serif-bold-italic mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-sans-serif-bold-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x41;</mtext></math>=<span>1D63C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x42;</mtext></math>=<span>1D63D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x43;</mtext></math>=<span>1D63E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x44;</mtext></math>=<span>1D63F</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x45;</mtext></math>=<span>1D640</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x46;</mtext></math>=<span>1D641</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x47;</mtext></math>=<span>1D642</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x48;</mtext></math>=<span>1D643</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x49;</mtext></math>=<span>1D644</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x4A;</mtext></math>=<span>1D645</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x4B;</mtext></math>=<span>1D646</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x4C;</mtext></math>=<span>1D647</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x4D;</mtext></math>=<span>1D648</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x4E;</mtext></math>=<span>1D649</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x4F;</mtext></math>=<span>1D64A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x50;</mtext></math>=<span>1D64B</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x51;</mtext></math>=<span>1D64C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x52;</mtext></math>=<span>1D64D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x53;</mtext></math>=<span>1D64E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x54;</mtext></math>=<span>1D64F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x55;</mtext></math>=<span>1D650</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x56;</mtext></math>=<span>1D651</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x57;</mtext></math>=<span>1D652</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x58;</mtext></math>=<span>1D653</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x59;</mtext></math>=<span>1D654</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x5A;</mtext></math>=<span>1D655</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x61;</mtext></math>=<span>1D656</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x62;</mtext></math>=<span>1D657</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x63;</mtext></math>=<span>1D658</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x64;</mtext></math>=<span>1D659</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x65;</mtext></math>=<span>1D65A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x66;</mtext></math>=<span>1D65B</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x67;</mtext></math>=<span>1D65C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x68;</mtext></math>=<span>1D65D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x69;</mtext></math>=<span>1D65E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x6A;</mtext></math>=<span>1D65F</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x6B;</mtext></math>=<span>1D660</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x6C;</mtext></math>=<span>1D661</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x6D;</mtext></math>=<span>1D662</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x6E;</mtext></math>=<span>1D663</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x6F;</mtext></math>=<span>1D664</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x70;</mtext></math>=<span>1D665</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x71;</mtext></math>=<span>1D666</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x72;</mtext></math>=<span>1D667</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x73;</mtext></math>=<span>1D668</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x74;</mtext></math>=<span>1D669</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x75;</mtext></math>=<span>1D66A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x76;</mtext></math>=<span>1D66B</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x77;</mtext></math>=<span>1D66C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x78;</mtext></math>=<span>1D66D</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x79;</mtext></math>=<span>1D66E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x7A;</mtext></math>=<span>1D66F</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x391;</mtext></math>=<span>1D790</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x392;</mtext></math>=<span>1D791</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x393;</mtext></math>=<span>1D792</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x394;</mtext></math>=<span>1D793</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x395;</mtext></math>=<span>1D794</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x396;</mtext></math>=<span>1D795</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x397;</mtext></math>=<span>1D796</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x398;</mtext></math>=<span>1D797</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x399;</mtext></math>=<span>1D798</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x39A;</mtext></math>=<span>1D799</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x39B;</mtext></math>=<span>1D79A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x39C;</mtext></math>=<span>1D79B</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x39D;</mtext></math>=<span>1D79C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x39E;</mtext></math>=<span>1D79D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x39F;</mtext></math>=<span>1D79E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A0;</mtext></math>=<span>1D79F</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A1;</mtext></math>=<span>1D7A0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3F4;</mtext></math>=<span>1D7A1</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A3;</mtext></math>=<span>1D7A2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A4;</mtext></math>=<span>1D7A3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A5;</mtext></math>=<span>1D7A4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A6;</mtext></math>=<span>1D7A5</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A7;</mtext></math>=<span>1D7A6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A8;</mtext></math>=<span>1D7A7</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3A9;</mtext></math>=<span>1D7A8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x2207;</mtext></math>=<span>1D7A9</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B1;</mtext></math>=<span>1D7AA</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B2;</mtext></math>=<span>1D7AB</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B3;</mtext></math>=<span>1D7AC</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B4;</mtext></math>=<span>1D7AD</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B5;</mtext></math>=<span>1D7AE</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B6;</mtext></math>=<span>1D7AF</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B7;</mtext></math>=<span>1D7B0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B8;</mtext></math>=<span>1D7B1</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3B9;</mtext></math>=<span>1D7B2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3BA;</mtext></math>=<span>1D7B3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3BB;</mtext></math>=<span>1D7B4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3BC;</mtext></math>=<span>1D7B5</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3BD;</mtext></math>=<span>1D7B6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3BE;</mtext></math>=<span>1D7B7</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3BF;</mtext></math>=<span>1D7B8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C0;</mtext></math>=<span>1D7B9</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C1;</mtext></math>=<span>1D7BA</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C2;</mtext></math>=<span>1D7BB</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C3;</mtext></math>=<span>1D7BC</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C4;</mtext></math>=<span>1D7BD</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C5;</mtext></math>=<span>1D7BE</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C6;</mtext></math>=<span>1D7BF</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C7;</mtext></math>=<span>1D7C0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C8;</mtext></math>=<span>1D7C1</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3C9;</mtext></math>=<span>1D7C2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x2202;</mtext></math>=<span>1D7C3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3F5;</mtext></math>=<span>1D7C4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3D1;</mtext></math>=<span>1D7C5</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3F0;</mtext></math>=<span>1D7C6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3D5;</mtext></math>=<span>1D7C7</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3F1;</mtext></math>=<span>1D7C8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-bold-italic">&#x3D6;</mtext></math>=<span>1D7C9</span></span><br/>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic-ref.html
new file mode 100644
index 0000000000..d7e23248c6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic-ref.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant sans-serif-italic (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-sans-serif-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D608;</mtext></math>=<span>1D608</span></span>
+ <span><math class="testfont"><mtext>&#x1D609;</mtext></math>=<span>1D609</span></span>
+ <span><math class="testfont"><mtext>&#x1D60A;</mtext></math>=<span>1D60A</span></span>
+ <span><math class="testfont"><mtext>&#x1D60B;</mtext></math>=<span>1D60B</span></span>
+ <span><math class="testfont"><mtext>&#x1D60C;</mtext></math>=<span>1D60C</span></span>
+ <span><math class="testfont"><mtext>&#x1D60D;</mtext></math>=<span>1D60D</span></span>
+ <span><math class="testfont"><mtext>&#x1D60E;</mtext></math>=<span>1D60E</span></span>
+ <span><math class="testfont"><mtext>&#x1D60F;</mtext></math>=<span>1D60F</span></span>
+ <span><math class="testfont"><mtext>&#x1D610;</mtext></math>=<span>1D610</span></span>
+ <span><math class="testfont"><mtext>&#x1D611;</mtext></math>=<span>1D611</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D612;</mtext></math>=<span>1D612</span></span>
+ <span><math class="testfont"><mtext>&#x1D613;</mtext></math>=<span>1D613</span></span>
+ <span><math class="testfont"><mtext>&#x1D614;</mtext></math>=<span>1D614</span></span>
+ <span><math class="testfont"><mtext>&#x1D615;</mtext></math>=<span>1D615</span></span>
+ <span><math class="testfont"><mtext>&#x1D616;</mtext></math>=<span>1D616</span></span>
+ <span><math class="testfont"><mtext>&#x1D617;</mtext></math>=<span>1D617</span></span>
+ <span><math class="testfont"><mtext>&#x1D618;</mtext></math>=<span>1D618</span></span>
+ <span><math class="testfont"><mtext>&#x1D619;</mtext></math>=<span>1D619</span></span>
+ <span><math class="testfont"><mtext>&#x1D61A;</mtext></math>=<span>1D61A</span></span>
+ <span><math class="testfont"><mtext>&#x1D61B;</mtext></math>=<span>1D61B</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D61C;</mtext></math>=<span>1D61C</span></span>
+ <span><math class="testfont"><mtext>&#x1D61D;</mtext></math>=<span>1D61D</span></span>
+ <span><math class="testfont"><mtext>&#x1D61E;</mtext></math>=<span>1D61E</span></span>
+ <span><math class="testfont"><mtext>&#x1D61F;</mtext></math>=<span>1D61F</span></span>
+ <span><math class="testfont"><mtext>&#x1D620;</mtext></math>=<span>1D620</span></span>
+ <span><math class="testfont"><mtext>&#x1D621;</mtext></math>=<span>1D621</span></span>
+ <span><math class="testfont"><mtext>&#x1D622;</mtext></math>=<span>1D622</span></span>
+ <span><math class="testfont"><mtext>&#x1D623;</mtext></math>=<span>1D623</span></span>
+ <span><math class="testfont"><mtext>&#x1D624;</mtext></math>=<span>1D624</span></span>
+ <span><math class="testfont"><mtext>&#x1D625;</mtext></math>=<span>1D625</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D626;</mtext></math>=<span>1D626</span></span>
+ <span><math class="testfont"><mtext>&#x1D627;</mtext></math>=<span>1D627</span></span>
+ <span><math class="testfont"><mtext>&#x1D628;</mtext></math>=<span>1D628</span></span>
+ <span><math class="testfont"><mtext>&#x1D629;</mtext></math>=<span>1D629</span></span>
+ <span><math class="testfont"><mtext>&#x1D62A;</mtext></math>=<span>1D62A</span></span>
+ <span><math class="testfont"><mtext>&#x1D62B;</mtext></math>=<span>1D62B</span></span>
+ <span><math class="testfont"><mtext>&#x1D62C;</mtext></math>=<span>1D62C</span></span>
+ <span><math class="testfont"><mtext>&#x1D62D;</mtext></math>=<span>1D62D</span></span>
+ <span><math class="testfont"><mtext>&#x1D62E;</mtext></math>=<span>1D62E</span></span>
+ <span><math class="testfont"><mtext>&#x1D62F;</mtext></math>=<span>1D62F</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D630;</mtext></math>=<span>1D630</span></span>
+ <span><math class="testfont"><mtext>&#x1D631;</mtext></math>=<span>1D631</span></span>
+ <span><math class="testfont"><mtext>&#x1D632;</mtext></math>=<span>1D632</span></span>
+ <span><math class="testfont"><mtext>&#x1D633;</mtext></math>=<span>1D633</span></span>
+ <span><math class="testfont"><mtext>&#x1D634;</mtext></math>=<span>1D634</span></span>
+ <span><math class="testfont"><mtext>&#x1D635;</mtext></math>=<span>1D635</span></span>
+ <span><math class="testfont"><mtext>&#x1D636;</mtext></math>=<span>1D636</span></span>
+ <span><math class="testfont"><mtext>&#x1D637;</mtext></math>=<span>1D637</span></span>
+ <span><math class="testfont"><mtext>&#x1D638;</mtext></math>=<span>1D638</span></span>
+ <span><math class="testfont"><mtext>&#x1D639;</mtext></math>=<span>1D639</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D63A;</mtext></math>=<span>1D63A</span></span>
+ <span><math class="testfont"><mtext>&#x1D63B;</mtext></math>=<span>1D63B</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic.html
new file mode 100644
index 0000000000..e847ca9bb8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-italic.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant sans-serif-italic</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#sans-serif-italic-mappings">
+<link rel="match" href="mathvariant-sans-serif-italic-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a sans-serif-italic mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-sans-serif-italic.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x41;</mtext></math>=<span>1D608</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x42;</mtext></math>=<span>1D609</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x43;</mtext></math>=<span>1D60A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x44;</mtext></math>=<span>1D60B</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x45;</mtext></math>=<span>1D60C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x46;</mtext></math>=<span>1D60D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x47;</mtext></math>=<span>1D60E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x48;</mtext></math>=<span>1D60F</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x49;</mtext></math>=<span>1D610</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x4A;</mtext></math>=<span>1D611</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x4B;</mtext></math>=<span>1D612</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x4C;</mtext></math>=<span>1D613</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x4D;</mtext></math>=<span>1D614</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x4E;</mtext></math>=<span>1D615</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x4F;</mtext></math>=<span>1D616</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x50;</mtext></math>=<span>1D617</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x51;</mtext></math>=<span>1D618</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x52;</mtext></math>=<span>1D619</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x53;</mtext></math>=<span>1D61A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x54;</mtext></math>=<span>1D61B</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x55;</mtext></math>=<span>1D61C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x56;</mtext></math>=<span>1D61D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x57;</mtext></math>=<span>1D61E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x58;</mtext></math>=<span>1D61F</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x59;</mtext></math>=<span>1D620</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x5A;</mtext></math>=<span>1D621</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x61;</mtext></math>=<span>1D622</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x62;</mtext></math>=<span>1D623</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x63;</mtext></math>=<span>1D624</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x64;</mtext></math>=<span>1D625</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x65;</mtext></math>=<span>1D626</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x66;</mtext></math>=<span>1D627</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x67;</mtext></math>=<span>1D628</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x68;</mtext></math>=<span>1D629</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x69;</mtext></math>=<span>1D62A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x6A;</mtext></math>=<span>1D62B</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x6B;</mtext></math>=<span>1D62C</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x6C;</mtext></math>=<span>1D62D</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x6D;</mtext></math>=<span>1D62E</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x6E;</mtext></math>=<span>1D62F</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x6F;</mtext></math>=<span>1D630</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x70;</mtext></math>=<span>1D631</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x71;</mtext></math>=<span>1D632</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x72;</mtext></math>=<span>1D633</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x73;</mtext></math>=<span>1D634</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x74;</mtext></math>=<span>1D635</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x75;</mtext></math>=<span>1D636</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x76;</mtext></math>=<span>1D637</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x77;</mtext></math>=<span>1D638</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x78;</mtext></math>=<span>1D639</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x79;</mtext></math>=<span>1D63A</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif-italic">&#x7A;</mtext></math>=<span>1D63B</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-ref.html
new file mode 100644
index 0000000000..ced5272ecb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif-ref.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant sans-serif (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-sans-serif.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D5A0;</mtext></math>=<span>1D5A0</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A1;</mtext></math>=<span>1D5A1</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A2;</mtext></math>=<span>1D5A2</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A3;</mtext></math>=<span>1D5A3</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A4;</mtext></math>=<span>1D5A4</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A5;</mtext></math>=<span>1D5A5</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A6;</mtext></math>=<span>1D5A6</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A7;</mtext></math>=<span>1D5A7</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A8;</mtext></math>=<span>1D5A8</span></span>
+ <span><math class="testfont"><mtext>&#x1D5A9;</mtext></math>=<span>1D5A9</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5AA;</mtext></math>=<span>1D5AA</span></span>
+ <span><math class="testfont"><mtext>&#x1D5AB;</mtext></math>=<span>1D5AB</span></span>
+ <span><math class="testfont"><mtext>&#x1D5AC;</mtext></math>=<span>1D5AC</span></span>
+ <span><math class="testfont"><mtext>&#x1D5AD;</mtext></math>=<span>1D5AD</span></span>
+ <span><math class="testfont"><mtext>&#x1D5AE;</mtext></math>=<span>1D5AE</span></span>
+ <span><math class="testfont"><mtext>&#x1D5AF;</mtext></math>=<span>1D5AF</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B0;</mtext></math>=<span>1D5B0</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B1;</mtext></math>=<span>1D5B1</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B2;</mtext></math>=<span>1D5B2</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B3;</mtext></math>=<span>1D5B3</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5B4;</mtext></math>=<span>1D5B4</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B5;</mtext></math>=<span>1D5B5</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B6;</mtext></math>=<span>1D5B6</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B7;</mtext></math>=<span>1D5B7</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B8;</mtext></math>=<span>1D5B8</span></span>
+ <span><math class="testfont"><mtext>&#x1D5B9;</mtext></math>=<span>1D5B9</span></span>
+ <span><math class="testfont"><mtext>&#x1D5BA;</mtext></math>=<span>1D5BA</span></span>
+ <span><math class="testfont"><mtext>&#x1D5BB;</mtext></math>=<span>1D5BB</span></span>
+ <span><math class="testfont"><mtext>&#x1D5BC;</mtext></math>=<span>1D5BC</span></span>
+ <span><math class="testfont"><mtext>&#x1D5BD;</mtext></math>=<span>1D5BD</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5BE;</mtext></math>=<span>1D5BE</span></span>
+ <span><math class="testfont"><mtext>&#x1D5BF;</mtext></math>=<span>1D5BF</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C0;</mtext></math>=<span>1D5C0</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C1;</mtext></math>=<span>1D5C1</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C2;</mtext></math>=<span>1D5C2</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C3;</mtext></math>=<span>1D5C3</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C4;</mtext></math>=<span>1D5C4</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C5;</mtext></math>=<span>1D5C5</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C6;</mtext></math>=<span>1D5C6</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C7;</mtext></math>=<span>1D5C7</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5C8;</mtext></math>=<span>1D5C8</span></span>
+ <span><math class="testfont"><mtext>&#x1D5C9;</mtext></math>=<span>1D5C9</span></span>
+ <span><math class="testfont"><mtext>&#x1D5CA;</mtext></math>=<span>1D5CA</span></span>
+ <span><math class="testfont"><mtext>&#x1D5CB;</mtext></math>=<span>1D5CB</span></span>
+ <span><math class="testfont"><mtext>&#x1D5CC;</mtext></math>=<span>1D5CC</span></span>
+ <span><math class="testfont"><mtext>&#x1D5CD;</mtext></math>=<span>1D5CD</span></span>
+ <span><math class="testfont"><mtext>&#x1D5CE;</mtext></math>=<span>1D5CE</span></span>
+ <span><math class="testfont"><mtext>&#x1D5CF;</mtext></math>=<span>1D5CF</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D0;</mtext></math>=<span>1D5D0</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D1;</mtext></math>=<span>1D5D1</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D5D2;</mtext></math>=<span>1D5D2</span></span>
+ <span><math class="testfont"><mtext>&#x1D5D3;</mtext></math>=<span>1D5D3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E2;</mtext></math>=<span>1D7E2</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E3;</mtext></math>=<span>1D7E3</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E4;</mtext></math>=<span>1D7E4</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E5;</mtext></math>=<span>1D7E5</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E6;</mtext></math>=<span>1D7E6</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E7;</mtext></math>=<span>1D7E7</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E8;</mtext></math>=<span>1D7E8</span></span>
+ <span><math class="testfont"><mtext>&#x1D7E9;</mtext></math>=<span>1D7E9</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D7EA;</mtext></math>=<span>1D7EA</span></span>
+ <span><math class="testfont"><mtext>&#x1D7EB;</mtext></math>=<span>1D7EB</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif.html
new file mode 100644
index 0000000000..43a1fa821f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-sans-serif.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant sans-serif</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#sans-serif-mappings">
+<link rel="match" href="mathvariant-sans-serif-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a sans-serif mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-sans-serif.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x41;</mtext></math>=<span>1D5A0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x42;</mtext></math>=<span>1D5A1</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x43;</mtext></math>=<span>1D5A2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x44;</mtext></math>=<span>1D5A3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x45;</mtext></math>=<span>1D5A4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x46;</mtext></math>=<span>1D5A5</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x47;</mtext></math>=<span>1D5A6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x48;</mtext></math>=<span>1D5A7</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x49;</mtext></math>=<span>1D5A8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x4A;</mtext></math>=<span>1D5A9</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x4B;</mtext></math>=<span>1D5AA</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x4C;</mtext></math>=<span>1D5AB</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x4D;</mtext></math>=<span>1D5AC</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x4E;</mtext></math>=<span>1D5AD</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x4F;</mtext></math>=<span>1D5AE</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x50;</mtext></math>=<span>1D5AF</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x51;</mtext></math>=<span>1D5B0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x52;</mtext></math>=<span>1D5B1</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x53;</mtext></math>=<span>1D5B2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x54;</mtext></math>=<span>1D5B3</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x55;</mtext></math>=<span>1D5B4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x56;</mtext></math>=<span>1D5B5</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x57;</mtext></math>=<span>1D5B6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x58;</mtext></math>=<span>1D5B7</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x59;</mtext></math>=<span>1D5B8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x5A;</mtext></math>=<span>1D5B9</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x61;</mtext></math>=<span>1D5BA</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x62;</mtext></math>=<span>1D5BB</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x63;</mtext></math>=<span>1D5BC</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x64;</mtext></math>=<span>1D5BD</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x65;</mtext></math>=<span>1D5BE</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x66;</mtext></math>=<span>1D5BF</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x67;</mtext></math>=<span>1D5C0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x68;</mtext></math>=<span>1D5C1</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x69;</mtext></math>=<span>1D5C2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x6A;</mtext></math>=<span>1D5C3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x6B;</mtext></math>=<span>1D5C4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x6C;</mtext></math>=<span>1D5C5</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x6D;</mtext></math>=<span>1D5C6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x6E;</mtext></math>=<span>1D5C7</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x6F;</mtext></math>=<span>1D5C8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x70;</mtext></math>=<span>1D5C9</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x71;</mtext></math>=<span>1D5CA</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x72;</mtext></math>=<span>1D5CB</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x73;</mtext></math>=<span>1D5CC</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x74;</mtext></math>=<span>1D5CD</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x75;</mtext></math>=<span>1D5CE</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x76;</mtext></math>=<span>1D5CF</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x77;</mtext></math>=<span>1D5D0</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x78;</mtext></math>=<span>1D5D1</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x79;</mtext></math>=<span>1D5D2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x7A;</mtext></math>=<span>1D5D3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x30;</mtext></math>=<span>1D7E2</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x31;</mtext></math>=<span>1D7E3</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x32;</mtext></math>=<span>1D7E4</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x33;</mtext></math>=<span>1D7E5</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x34;</mtext></math>=<span>1D7E6</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x35;</mtext></math>=<span>1D7E7</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x36;</mtext></math>=<span>1D7E8</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x37;</mtext></math>=<span>1D7E9</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x38;</mtext></math>=<span>1D7EA</span></span>
+ <span><math class="testfont"><mtext mathvariant="sans-serif">&#x39;</mtext></math>=<span>1D7EB</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script-ref.html
new file mode 100644
index 0000000000..60ced85bc3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script-ref.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant script (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-script.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1D49C;</mtext></math>=<span>1D49C</span></span>
+ <span><math class="testfont"><mtext>&#x212C;</mtext></math>=<span>0212C</span></span>
+ <span><math class="testfont"><mtext>&#x1D49E;</mtext></math>=<span>1D49E</span></span>
+ <span><math class="testfont"><mtext>&#x1D49F;</mtext></math>=<span>1D49F</span></span>
+ <span><math class="testfont"><mtext>&#x2130;</mtext></math>=<span>02130</span></span>
+ <span><math class="testfont"><mtext>&#x2131;</mtext></math>=<span>02131</span></span>
+ <span><math class="testfont"><mtext>&#x1D4A2;</mtext></math>=<span>1D4A2</span></span>
+ <span><math class="testfont"><mtext>&#x210B;</mtext></math>=<span>0210B</span></span>
+ <span><math class="testfont"><mtext>&#x2110;</mtext></math>=<span>02110</span></span>
+ <span><math class="testfont"><mtext>&#x1D4A5;</mtext></math>=<span>1D4A5</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4A6;</mtext></math>=<span>1D4A6</span></span>
+ <span><math class="testfont"><mtext>&#x2112;</mtext></math>=<span>02112</span></span>
+ <span><math class="testfont"><mtext>&#x2133;</mtext></math>=<span>02133</span></span>
+ <span><math class="testfont"><mtext>&#x1D4A9;</mtext></math>=<span>1D4A9</span></span>
+ <span><math class="testfont"><mtext>&#x1D4AA;</mtext></math>=<span>1D4AA</span></span>
+ <span><math class="testfont"><mtext>&#x1D4AB;</mtext></math>=<span>1D4AB</span></span>
+ <span><math class="testfont"><mtext>&#x1D4AC;</mtext></math>=<span>1D4AC</span></span>
+ <span><math class="testfont"><mtext>&#x211B;</mtext></math>=<span>0211B</span></span>
+ <span><math class="testfont"><mtext>&#x1D4AE;</mtext></math>=<span>1D4AE</span></span>
+ <span><math class="testfont"><mtext>&#x1D4AF;</mtext></math>=<span>1D4AF</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4B0;</mtext></math>=<span>1D4B0</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B1;</mtext></math>=<span>1D4B1</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B2;</mtext></math>=<span>1D4B2</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B3;</mtext></math>=<span>1D4B3</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B4;</mtext></math>=<span>1D4B4</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B5;</mtext></math>=<span>1D4B5</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B6;</mtext></math>=<span>1D4B6</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B7;</mtext></math>=<span>1D4B7</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B8;</mtext></math>=<span>1D4B8</span></span>
+ <span><math class="testfont"><mtext>&#x1D4B9;</mtext></math>=<span>1D4B9</span></span><br/>
+ <span><math class="testfont"><mtext>&#x212F;</mtext></math>=<span>0212F</span></span>
+ <span><math class="testfont"><mtext>&#x1D4BB;</mtext></math>=<span>1D4BB</span></span>
+ <span><math class="testfont"><mtext>&#x210A;</mtext></math>=<span>0210A</span></span>
+ <span><math class="testfont"><mtext>&#x1D4BD;</mtext></math>=<span>1D4BD</span></span>
+ <span><math class="testfont"><mtext>&#x1D4BE;</mtext></math>=<span>1D4BE</span></span>
+ <span><math class="testfont"><mtext>&#x1D4BF;</mtext></math>=<span>1D4BF</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C0;</mtext></math>=<span>1D4C0</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C1;</mtext></math>=<span>1D4C1</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C2;</mtext></math>=<span>1D4C2</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C3;</mtext></math>=<span>1D4C3</span></span><br/>
+ <span><math class="testfont"><mtext>&#x2134;</mtext></math>=<span>02134</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C5;</mtext></math>=<span>1D4C5</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C6;</mtext></math>=<span>1D4C6</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C7;</mtext></math>=<span>1D4C7</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C8;</mtext></math>=<span>1D4C8</span></span>
+ <span><math class="testfont"><mtext>&#x1D4C9;</mtext></math>=<span>1D4C9</span></span>
+ <span><math class="testfont"><mtext>&#x1D4CA;</mtext></math>=<span>1D4CA</span></span>
+ <span><math class="testfont"><mtext>&#x1D4CB;</mtext></math>=<span>1D4CB</span></span>
+ <span><math class="testfont"><mtext>&#x1D4CC;</mtext></math>=<span>1D4CC</span></span>
+ <span><math class="testfont"><mtext>&#x1D4CD;</mtext></math>=<span>1D4CD</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1D4CE;</mtext></math>=<span>1D4CE</span></span>
+ <span><math class="testfont"><mtext>&#x1D4CF;</mtext></math>=<span>1D4CF</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script.html
new file mode 100644
index 0000000000..afbdfd9f94
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-script.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant script</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#script-mappings">
+<link rel="match" href="mathvariant-script-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a script mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-script.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="script">&#x41;</mtext></math>=<span>1D49C</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x42;</mtext></math>=<span>0212C</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x43;</mtext></math>=<span>1D49E</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x44;</mtext></math>=<span>1D49F</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x45;</mtext></math>=<span>02130</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x46;</mtext></math>=<span>02131</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x47;</mtext></math>=<span>1D4A2</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x48;</mtext></math>=<span>0210B</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x49;</mtext></math>=<span>02110</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x4A;</mtext></math>=<span>1D4A5</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="script">&#x4B;</mtext></math>=<span>1D4A6</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x4C;</mtext></math>=<span>02112</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x4D;</mtext></math>=<span>02133</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x4E;</mtext></math>=<span>1D4A9</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x4F;</mtext></math>=<span>1D4AA</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x50;</mtext></math>=<span>1D4AB</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x51;</mtext></math>=<span>1D4AC</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x52;</mtext></math>=<span>0211B</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x53;</mtext></math>=<span>1D4AE</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x54;</mtext></math>=<span>1D4AF</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="script">&#x55;</mtext></math>=<span>1D4B0</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x56;</mtext></math>=<span>1D4B1</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x57;</mtext></math>=<span>1D4B2</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x58;</mtext></math>=<span>1D4B3</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x59;</mtext></math>=<span>1D4B4</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x5A;</mtext></math>=<span>1D4B5</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x61;</mtext></math>=<span>1D4B6</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x62;</mtext></math>=<span>1D4B7</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x63;</mtext></math>=<span>1D4B8</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x64;</mtext></math>=<span>1D4B9</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="script">&#x65;</mtext></math>=<span>0212F</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x66;</mtext></math>=<span>1D4BB</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x67;</mtext></math>=<span>0210A</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x68;</mtext></math>=<span>1D4BD</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x69;</mtext></math>=<span>1D4BE</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x6A;</mtext></math>=<span>1D4BF</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x6B;</mtext></math>=<span>1D4C0</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x6C;</mtext></math>=<span>1D4C1</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x6D;</mtext></math>=<span>1D4C2</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x6E;</mtext></math>=<span>1D4C3</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="script">&#x6F;</mtext></math>=<span>02134</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x70;</mtext></math>=<span>1D4C5</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x71;</mtext></math>=<span>1D4C6</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x72;</mtext></math>=<span>1D4C7</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x73;</mtext></math>=<span>1D4C8</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x74;</mtext></math>=<span>1D4C9</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x75;</mtext></math>=<span>1D4CA</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x76;</mtext></math>=<span>1D4CB</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x77;</mtext></math>=<span>1D4CC</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x78;</mtext></math>=<span>1D4CD</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="script">&#x79;</mtext></math>=<span>1D4CE</span></span>
+ <span><math class="testfont"><mtext mathvariant="script">&#x7A;</mtext></math>=<span>1D4CF</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched-ref.html
new file mode 100644
index 0000000000..454f2004ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched-ref.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant stretched (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-stretched.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1EE61;</mtext></math>=<span>1EE61</span></span>
+ <span><math class="testfont"><mtext>&#x1EE62;</mtext></math>=<span>1EE62</span></span>
+ <span><math class="testfont"><mtext>&#x1EE64;</mtext></math>=<span>1EE64</span></span>
+ <span><math class="testfont"><mtext>&#x1EE67;</mtext></math>=<span>1EE67</span></span>
+ <span><math class="testfont"><mtext>&#x1EE68;</mtext></math>=<span>1EE68</span></span>
+ <span><math class="testfont"><mtext>&#x1EE69;</mtext></math>=<span>1EE69</span></span>
+ <span><math class="testfont"><mtext>&#x1EE6A;</mtext></math>=<span>1EE6A</span></span>
+ <span><math class="testfont"><mtext>&#x1EE6C;</mtext></math>=<span>1EE6C</span></span>
+ <span><math class="testfont"><mtext>&#x1EE6D;</mtext></math>=<span>1EE6D</span></span>
+ <span><math class="testfont"><mtext>&#x1EE6E;</mtext></math>=<span>1EE6E</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EE6F;</mtext></math>=<span>1EE6F</span></span>
+ <span><math class="testfont"><mtext>&#x1EE70;</mtext></math>=<span>1EE70</span></span>
+ <span><math class="testfont"><mtext>&#x1EE71;</mtext></math>=<span>1EE71</span></span>
+ <span><math class="testfont"><mtext>&#x1EE72;</mtext></math>=<span>1EE72</span></span>
+ <span><math class="testfont"><mtext>&#x1EE74;</mtext></math>=<span>1EE74</span></span>
+ <span><math class="testfont"><mtext>&#x1EE75;</mtext></math>=<span>1EE75</span></span>
+ <span><math class="testfont"><mtext>&#x1EE76;</mtext></math>=<span>1EE76</span></span>
+ <span><math class="testfont"><mtext>&#x1EE77;</mtext></math>=<span>1EE77</span></span>
+ <span><math class="testfont"><mtext>&#x1EE79;</mtext></math>=<span>1EE79</span></span>
+ <span><math class="testfont"><mtext>&#x1EE7A;</mtext></math>=<span>1EE7A</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EE7B;</mtext></math>=<span>1EE7B</span></span>
+ <span><math class="testfont"><mtext>&#x1EE7C;</mtext></math>=<span>1EE7C</span></span>
+ <span><math class="testfont"><mtext>&#x1EE7E;</mtext></math>=<span>1EE7E</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched.html
new file mode 100644
index 0000000000..99b06fb41b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-stretched.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant stretched</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#stretched-mappings">
+<link rel="match" href="mathvariant-stretched-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a stretched mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-stretched.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x628;</mtext></math>=<span>1EE61</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x62C;</mtext></math>=<span>1EE62</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x647;</mtext></math>=<span>1EE64</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x62D;</mtext></math>=<span>1EE67</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x637;</mtext></math>=<span>1EE68</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x64A;</mtext></math>=<span>1EE69</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x643;</mtext></math>=<span>1EE6A</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x645;</mtext></math>=<span>1EE6C</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x646;</mtext></math>=<span>1EE6D</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x633;</mtext></math>=<span>1EE6E</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x639;</mtext></math>=<span>1EE6F</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x641;</mtext></math>=<span>1EE70</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x635;</mtext></math>=<span>1EE71</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x642;</mtext></math>=<span>1EE72</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x634;</mtext></math>=<span>1EE74</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x62A;</mtext></math>=<span>1EE75</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x62B;</mtext></math>=<span>1EE76</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x62E;</mtext></math>=<span>1EE77</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x636;</mtext></math>=<span>1EE79</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x638;</mtext></math>=<span>1EE7A</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x63A;</mtext></math>=<span>1EE7B</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x66E;</mtext></math>=<span>1EE7C</span></span>
+ <span><math class="testfont"><mtext mathvariant="stretched">&#x6A1;</mtext></math>=<span>1EE7E</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed-ref.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed-ref.html
new file mode 100644
index 0000000000..5e1461cd33
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant tailed (reference)</title>
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-tailed.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext>&#x1EE42;</mtext></math>=<span>1EE42</span></span>
+ <span><math class="testfont"><mtext>&#x1EE47;</mtext></math>=<span>1EE47</span></span>
+ <span><math class="testfont"><mtext>&#x1EE49;</mtext></math>=<span>1EE49</span></span>
+ <span><math class="testfont"><mtext>&#x1EE4B;</mtext></math>=<span>1EE4B</span></span>
+ <span><math class="testfont"><mtext>&#x1EE4D;</mtext></math>=<span>1EE4D</span></span>
+ <span><math class="testfont"><mtext>&#x1EE4E;</mtext></math>=<span>1EE4E</span></span>
+ <span><math class="testfont"><mtext>&#x1EE4F;</mtext></math>=<span>1EE4F</span></span>
+ <span><math class="testfont"><mtext>&#x1EE51;</mtext></math>=<span>1EE51</span></span>
+ <span><math class="testfont"><mtext>&#x1EE52;</mtext></math>=<span>1EE52</span></span>
+ <span><math class="testfont"><mtext>&#x1EE54;</mtext></math>=<span>1EE54</span></span><br/>
+ <span><math class="testfont"><mtext>&#x1EE57;</mtext></math>=<span>1EE57</span></span>
+ <span><math class="testfont"><mtext>&#x1EE59;</mtext></math>=<span>1EE59</span></span>
+ <span><math class="testfont"><mtext>&#x1EE5B;</mtext></math>=<span>1EE5B</span></span>
+ <span><math class="testfont"><mtext>&#x1EE5D;</mtext></math>=<span>1EE5D</span></span>
+ <span><math class="testfont"><mtext>&#x1EE5F;</mtext></math>=<span>1EE5F</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed.html b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed.html
new file mode 100644
index 0000000000..43fbca5e80
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mathvariant/mathvariant-tailed.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<title>mathvariant tailed</title>
+<link rel="help" href="https://w3c.github.io/mathml-core/#css-styling">
+<link rel="help" href="https://w3c.github.io/mathml-core/#the-mathvariant-attribute">
+<link rel="help" href="https://w3c.github.io/mathml-core/#new-text-transform-values">
+<link rel="help" href="https://w3c.github.io/mathml-core/#tailed-mappings">
+<link rel="match" href="mathvariant-tailed-ref.html"/>
+<meta name="assert" content="Verify that a single-char <mtext> with a tailed mathvariant is equivalent to an <mtext> with the transformed unicode character.">
+<style>
+ @font-face {
+ font-family: TestFont;
+ src: url("/fonts/math/mathvariant-tailed.woff");
+ }
+ body > span {
+ padding: 10px;
+ }
+ span > span {
+ font-family: monospace;
+ font-size: 10px;
+ }
+ .testfont {
+ font-family: TestFont;
+ font-size: 10px;
+ }
+</style>
+<body>
+ <!-- Generated by mathml/tools/mathvariant.py; DO NOT EDIT. -->
+ <p>Test passes if all the equalities below are true.</p>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x62C;</mtext></math>=<span>1EE42</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x62D;</mtext></math>=<span>1EE47</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x64A;</mtext></math>=<span>1EE49</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x644;</mtext></math>=<span>1EE4B</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x646;</mtext></math>=<span>1EE4D</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x633;</mtext></math>=<span>1EE4E</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x639;</mtext></math>=<span>1EE4F</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x635;</mtext></math>=<span>1EE51</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x642;</mtext></math>=<span>1EE52</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x634;</mtext></math>=<span>1EE54</span></span><br/>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x62E;</mtext></math>=<span>1EE57</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x636;</mtext></math>=<span>1EE59</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x63A;</mtext></math>=<span>1EE5B</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x6BA;</mtext></math>=<span>1EE5D</span></span>
+ <span><math class="testfont"><mtext mathvariant="tailed">&#x66F;</mtext></math>=<span>1EE5F</span></span>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1-ref.html
new file mode 100644
index 0000000000..2302a2b8d8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1-ref.html
@@ -0,0 +1,21 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1a.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1a.html
new file mode 100644
index 0000000000..bbf7c780fe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1a.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="longdiv">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1b.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1b.html
new file mode 100644
index 0000000000..3e0675f73e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1b.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="actuarial">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1d.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1d.html
new file mode 100644
index 0000000000..4fe5e9b68b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1d.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="box">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1e.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1e.html
new file mode 100644
index 0000000000..10761f394a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1e.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="roundedbox">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1f.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1f.html
new file mode 100644
index 0000000000..4dbde38a5e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1f.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="circle">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1g.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1g.html
new file mode 100644
index 0000000000..0a5ffa1df0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1g.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="left">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1h.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1h.html
new file mode 100644
index 0000000000..038d2e64d4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1h.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="right">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1i.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1i.html
new file mode 100644
index 0000000000..ee4070402c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1i.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="top">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1j.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1j.html
new file mode 100644
index 0000000000..e1f7fcac2f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1j.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="bottom">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1k.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1k.html
new file mode 100644
index 0000000000..296b97dee0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1k.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="updiagonalstrike">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1l.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1l.html
new file mode 100644
index 0000000000..7ba2a7d312
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1l.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="downdiagonalstrike">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1m.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1m.html
new file mode 100644
index 0000000000..de7223a55e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1m.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="verticalstrike">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1n.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1n.html
new file mode 100644
index 0000000000..0578ee9237
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1n.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="horizontalstrike">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1o.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1o.html
new file mode 100644
index 0000000000..17f7a6b183
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1o.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="madruwb">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1p.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1p.html
new file mode 100644
index 0000000000..e4f6b2dcc4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1p.html
@@ -0,0 +1,22 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: test that attributes have an effect -->
+ <math>
+ <menclose notation="updiagonalarrow">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1q.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1q.html
new file mode 100644
index 0000000000..5bb1930c48
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-1q.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose</title>
+ <link rel="mismatch" href="menclose-1-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <menclose notation="phasorangle">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial-ref.html
new file mode 100644
index 0000000000..b63e84ac84
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose actuarial</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) +
+ "L" + ((box.width+box.left) + "," + box.top) +
+ "L" + ((box.width+box.left) + "," + box.bottom ));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="actuarial">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial.html
new file mode 100644
index 0000000000..2fb3abcc73
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-actuarial.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose actuarial</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-actuarial-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) +
+ "L" + ((box.width+box.left) + "," + box.top) +
+ "L" + ((box.width+box.left) + "," + box.bottom ));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="actuarial">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom-ref.html
new file mode 100644
index 0000000000..491e223f94
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose bottom</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.top + box.height)) + " " +
+ "l" + (box.width + "," + 0));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="bottom">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom.html
new file mode 100644
index 0000000000..a0e6018c76
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-bottom.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose bottom</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-bottom-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.top + box.height)) + " " +
+ "l" + (box.width + "," + 0));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="bottom">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box-ref.html
new file mode 100644
index 0000000000..20bad27489
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose box</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ r = document.getElementById("rect");
+ r.setAttribute("x", box.left );
+ r.setAttribute("y", box.top );
+ r.setAttribute("width", box.width );
+ r.setAttribute("height", box.height );
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="box">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <rect id="rect" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></rect>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box.html
new file mode 100644
index 0000000000..dc5ed38dc9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-box.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose box</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-box-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ r = document.getElementById("rect");
+ r.setAttribute("x", box.left );
+ r.setAttribute("y", box.top );
+ r.setAttribute("width", box.width );
+ r.setAttribute("height", box.height );
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="box">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <rect id="rect" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></rect>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle-ref.html
new file mode 100644
index 0000000000..f1b98e7864
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose circle</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("circle").getBoundingClientRect();
+ e = document.getElementById("ellipse");
+ e.setAttribute("cx", (box.left + box.width/2));
+ e.setAttribute("rx", (box.width/2));
+ e.setAttribute("cy", (box.top + box.height/2));
+ e.setAttribute("ry", (box.height/2));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="circle" notation="circle">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <ellipse id="ellipse" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></ellipse>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle.html
new file mode 100644
index 0000000000..58b627add2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-circle.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose circle</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-circle-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("circle").getBoundingClientRect();
+ e = document.getElementById("ellipse");
+ e.setAttribute("cx", (box.left + box.width/2));
+ e.setAttribute("rx", (box.width/2));
+ e.setAttribute("cy", (box.top + box.height/2));
+ e.setAttribute("ry", (box.height/2));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="circle" notation="circle">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <ellipse id="ellipse" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></ellipse>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike-ref.html
new file mode 100644
index 0000000000..bc7aad0ea8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose downdiagonalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) + " " +
+ "l" + (box.width + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="downdiagonalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike.html
new file mode 100644
index 0000000000..257dc36c3b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-downdiagonalstrike.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose downdiagonalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-downdiagonalstrike-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) + " " +
+ "l" + (box.width + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="downdiagonalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike-ref.html
new file mode 100644
index 0000000000..973d1fc752
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose horizontalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.top + box.bottom)/2 ) + " " +
+ "l" + (box.width + "," + 0));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="horizontalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;" ></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike.html
new file mode 100644
index 0000000000..3db832d8d6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-horizontalstrike.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose horizontalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-horizontalstrike-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.top + box.bottom)/2 ) + " " +
+ "l" + (box.width + "," + 0));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="horizontalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left-ref.html
new file mode 100644
index 0000000000..760ef8a89f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose left</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) + " " +
+ "l" + (0 + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="left">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left.html
new file mode 100644
index 0000000000..d1b554ec62
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-left.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose left</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-left-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) + " " +
+ "l" + (0 + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="left">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv-ref.html
new file mode 100644
index 0000000000..b67504af50
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose longdiv</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ var x = " " + box.left + "," + box.top + " " ;
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.top + box.height)) +
+ "Q" + ((box.left + 15) + "," + (box.top + box.height)/2) + x +
+ "L" + ((box.left + box.width) + "," + box.top));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="longdiv">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: 1em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv.html
new file mode 100644
index 0000000000..fa12b8934d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-longdiv.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose longdiv</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-longdiv-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ var x = " " + box.left + "," + box.top + " " ;
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.top + box.height)) +
+ "Q" + ((box.left + 15) + "," + (box.top + box.height)/2) + x +
+ "L" + ((box.left + box.width) + "," + box.top));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="longdiv">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: 1em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle-ref.html
new file mode 100644
index 0000000000..b3d91a9fbe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose phasorangle</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ var w = 8 * 2; // kPhasorangleWidth * rulethickness
+ var H = 2 * w; // slope 2
+
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.right + "," + box.bottom) +
+ "L" + (box.left + "," + box.bottom ) +
+ "l" + ((w) + "," + (-H)));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="phasorangle">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle.html
new file mode 100644
index 0000000000..55b3dc2faf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-phasorangle.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose phasorangle</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-phasorangle-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ var w = 8 * 2; // kPhasorangleWidth * rulethickness
+ var H = 2 * w; // slope 2
+
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.right + "," + box.bottom) +
+ "L" + (box.left + "," + box.bottom ) +
+ "l" + ((w) + "," + (-H)));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="phasorangle">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right-ref.html
new file mode 100644
index 0000000000..acd3e30dd2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose right</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + ((box.left + box.width) + "," + box.top) + " " +
+ "l" + (0 + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="right">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right.html
new file mode 100644
index 0000000000..b64ca8cc96
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-right.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose right</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-right-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + ((box.left + box.width) + "," + box.top) + " " +
+ "l" + (0 + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="right">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox-ref.html
new file mode 100644
index 0000000000..c1f0225036
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8"/>
+ <title>menclose roundedbox</title>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, #box {
+ font: 25px/1 Ahem;
+ }
+ </style>
+
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("roundedbox").getBoundingClientRect();
+ r = document.getElementById("box");
+ r.style.left = `calc(${box.left}px - .25em)`;
+ r.style.top = `calc(${box.top}px - .25em)`
+ r.style.width = `calc(${box.width}px + .5em)`;
+ r.style.height = `calc(${box.height}px + .5em)`;
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="roundedbox" notation="roundedbox">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div id="box" style="position: absolute; box-sizing: border-box; border:.5em solid green; border-radius: .5em;"></div>
+</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox.html
new file mode 100644
index 0000000000..f31e696ce1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-roundedbox.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8"/>
+ <title>menclose roundedbox</title>
+ <link rel="match" href="menclose-2-roundedbox-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, #box {
+ font: 25px/1 Ahem;
+ }
+ </style>
+
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("roundedbox").getBoundingClientRect();
+ r = document.getElementById("box");
+ r.style.left = `calc(${box.left}px - .25em)`;
+ r.style.top = `calc(${box.top}px - .25em)`
+ r.style.width = `calc(${box.width}px + .5em)`;
+ r.style.height = `calc(${box.height}px + .5em)`;
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="roundedbox" notation="roundedbox">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div id="box" style="position: absolute; box-sizing: border-box; border:.5em solid green; border-radius: .5em;"></div>
+</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top-ref.html
new file mode 100644
index 0000000000..31ea1d8c7e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose top</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) + " " +
+ "l" + (box.width + "," + 0));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="top">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top.html
new file mode 100644
index 0000000000..a135a21afc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-top.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose top</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-top-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.top) + " " +
+ "l" + (box.width + "," + 0));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="top">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow-ref.html
new file mode 100644
index 0000000000..5e1c37317b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalarrow</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.height + box.top)) +
+ "L" + ((box.width + box.left) + "," + box.top) +
+ "l -15,0 l0,+15 l+15,0 l0,-15"); // try and hide the arrow head with a square.
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="updiagonalarrow">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow.html
new file mode 100644
index 0000000000..f0d4fb1dbf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalarrow.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalarrow</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-updiagonalarrow-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + (box.height + box.top)) +
+ "L" + ((box.width + box.left) + "," + box.top) +
+ "l -15,0 l0,+15 l+15,0 l0,-15"); // try and hide the arrow head with a square.
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="updiagonalarrow">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike-ref.html
new file mode 100644
index 0000000000..dc99162c34
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.bottom) + " " +
+ "l" + (box.width + "," + (-box.height)));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="updiagonalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike.html
new file mode 100644
index 0000000000..5ab256e5f2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-updiagonalstrike.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-updiagonalstrike-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + (box.left + "," + box.bottom) + " " +
+ "l" + (box.width + "," + (-box.height)));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="updiagonalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike-ref.html
new file mode 100644
index 0000000000..d5ba9ff227
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose verticalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + ((box.right + box.left)/2 + "," + box.top) + " " +
+ "l" + (0 + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <mphantom>
+ <menclose id="box" notation="verticalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </mphantom>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike.html
new file mode 100644
index 0000000000..b40a452bbe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-2-verticalstrike.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose verticalstrike</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-2-verticalstrike-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ math, svg {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box = document.getElementById("box").getBoundingClientRect();
+ document.getElementById("path").setAttribute("d",
+ "M" + ((box.right + box.left)/2 + "," + box.top) + " " +
+ "l" + (0 + "," + box.height));
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="box" notation="verticalstrike">
+ <mspace width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+
+ <div style="position: absolute; left: 0px; top: 0px;">
+ <svg width="500px" height="500px">
+ <path id="path" style="fill: none; stroke-width: .5em; stroke: green; stroke-linecap: round; shape-rendering: crispEdges;"></path>
+ </svg>
+ </div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box-ref.html
new file mode 100644
index 0000000000..0d718ee05c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose box</title>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="left top right bottom">
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box.html
new file mode 100644
index 0000000000..a28ba547cf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-box.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose box</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-3-box-ref.html"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="box">
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default-ref.html
new file mode 100644
index 0000000000..db9d4375ad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose default</title>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose>
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default.html
new file mode 100644
index 0000000000..f1f8be193c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-default.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose default</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-3-default-ref.html"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="longdiv">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid-ref.html
new file mode 100644
index 0000000000..dc0740763d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose invalid</title>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <math>
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid.html
new file mode 100644
index 0000000000..e210c97fb0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-invalid.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose invalid</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-3-invalid-ref.html"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="not_a_vaild_notation">
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb-ref.html
new file mode 100644
index 0000000000..58ed5903b4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose madruwb</title>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <!-- menclose: madruwb -->
+ <math>
+ <menclose notation="right bottom"><mi>x</mi></menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb.html
new file mode 100644
index 0000000000..ff8e4540bf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-madruwb.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test menclose madruwb</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-3-madruwb-ref.html"/>
+ </head>
+
+ <body>
+ <!-- menclose: madruwb -->
+ <math>
+ <menclose notation="madruwb"><mi>x</mi></menclose>
+ </math>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple-ref.html
new file mode 100644
index 0000000000..c42a504bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose multiple</title>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="top top left circle">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple.html
new file mode 100644
index 0000000000..ea8b77f7fa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-multiple.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose multiple</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-3-multiple-ref.html"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="circle left top">
+ <mspace width="100px" height="50px"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown-ref.html
new file mode 100644
index 0000000000..20f3a72eb9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose unknown</title>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="circle">
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown.html
new file mode 100644
index 0000000000..16587da798
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-3-unknown.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose unknown</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="menclose-3-unknown-ref.html"/>
+ </head>
+
+ <body>
+ <math>
+ <menclose notation="circle unknown">
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </menclose>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-4.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-4.html
new file mode 100644
index 0000000000..1b27e96af9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-4.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose inner and outer circle</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="data:text/html,<body>Pass"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var box1 = document.getElementById("outer").getBoundingClientRect();
+ var box2 = document.getElementById("inner").getBoundingClientRect();
+ var epsilon = 0.1;
+ if ((box1.width >= ((Math.sqrt(2) - epsilon)*box2.width)) && (box1.height >= ((Math.sqrt(2) - epsilon)*box2.height))) {
+ document.body.innerHTML = "Pass";
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <div style="position: absolute; left: 20px; top: 20px;">
+ <math>
+ <menclose id="outer" notation="circle">
+ <mspace id="inner" width="200px" height="100px"></mspace>
+ </menclose>
+ </math>
+ </div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial-ref.html
new file mode 100644
index 0000000000..2369d4e232
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose actuarial</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="actuarial"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial.html
new file mode 100644
index 0000000000..707a8e7933
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-actuarial.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose actuarial</title>
+ <link rel="match" href="menclose-5-actuarial-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","actuarial");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom-ref.html
new file mode 100644
index 0000000000..5165333ddd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose bottom</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="bottom"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom.html
new file mode 100644
index 0000000000..aab45e679f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-bottom.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose bottom</title>
+ <link rel="match" href="menclose-5-bottom-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","bottom");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box-ref.html
new file mode 100644
index 0000000000..dfe4a3d0f3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose box</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="box"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box.html
new file mode 100644
index 0000000000..548f8b5e65
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-box.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose box</title>
+ <link rel="match" href="menclose-5-box-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","box");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle-ref.html
new file mode 100644
index 0000000000..2132e0ebfd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose circle</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="circle"><mspace width="100px" height="50px"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle.html
new file mode 100644
index 0000000000..fdbc0b4d97
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-circle.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose circle</title>
+ <link rel="match" href="menclose-5-circle-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","circle");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike-ref.html
new file mode 100644
index 0000000000..b37bfd0ffb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose downdiagonalstrike</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="downdiagonalstrike"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike.html
new file mode 100644
index 0000000000..82c89e6c9d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-downdiagonalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose downdiagonalstrike</title>
+ <link rel="match" href="menclose-5-downdiagonalstrike-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","downdiagonalstrike");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike-ref.html
new file mode 100644
index 0000000000..777314e09e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose horizontalstrike</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="horizontalstrike"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike.html
new file mode 100644
index 0000000000..fc2ee04284
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-horizontalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose horizontalstrike</title>
+ <link rel="match" href="menclose-5-horizontalstrike-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","horizontalstrike");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left-ref.html
new file mode 100644
index 0000000000..7c27e231eb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose left</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="left"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left.html
new file mode 100644
index 0000000000..5f5b98af2a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-left.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose left</title>
+ <link rel="match" href="menclose-5-left-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","left");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv-ref.html
new file mode 100644
index 0000000000..1c1763fd1e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose longdiv</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="longdiv"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv.html
new file mode 100644
index 0000000000..0444bd5fbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-longdiv.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose longdiv</title>
+ <link rel="match" href="menclose-5-longdiv-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","longdiv");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb-ref.html
new file mode 100644
index 0000000000..0aeb0396d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose madruwb</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="madruwb"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb.html
new file mode 100644
index 0000000000..cdd150ac3e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-madruwb.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose madruwb</title>
+ <link rel="match" href="menclose-5-madruwb-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","madruwb");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle-ref.html
new file mode 100644
index 0000000000..5fde01d685
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose actuarial</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="phasorangle"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle.html
new file mode 100644
index 0000000000..098a8a24d9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-phasorangle.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose phasorangle</title>
+ <link rel="match" href="menclose-5-phasorangle-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","phasorangle");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right-ref.html
new file mode 100644
index 0000000000..059a8b983d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose right</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="right"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right.html
new file mode 100644
index 0000000000..f337bdaeb7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-right.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose right</title>
+ <link rel="match" href="menclose-5-right-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","right");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox-ref.html
new file mode 100644
index 0000000000..cd696e8249
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose roundedbox</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="roundedbox"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox.html
new file mode 100644
index 0000000000..86a89c4626
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-roundedbox.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose roundedbox</title>
+ <link rel="match" href="menclose-5-roundedbox-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","roundedbox");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top-ref.html
new file mode 100644
index 0000000000..7799e08457
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose top</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="top"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top.html
new file mode 100644
index 0000000000..a173f8354f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-top.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose top</title>
+ <link rel="match" href="menclose-5-top-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","top");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow-ref.html
new file mode 100644
index 0000000000..6de10f03c9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose updiagonalarrow</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="updiagonalarrow"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow.html
new file mode 100644
index 0000000000..ba5409f0e9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalarrow.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalarrow</title>
+ <link rel="match" href="menclose-5-updiagonalarrow-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","updiagonalarrow");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike-ref.html
new file mode 100644
index 0000000000..24599bbdfd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose updiagonalstrike</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="updiagonalstrike"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike.html
new file mode 100644
index 0000000000..3a093b3540
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-updiagonalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalstrike</title>
+ <link rel="match" href="menclose-5-updiagonalstrike-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","updiagonalstrike");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike-ref.html
new file mode 100644
index 0000000000..50b7112912
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose verticalstrike</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="verticalstrike"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike.html
new file mode 100644
index 0000000000..561c35c498
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-5-verticalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose verticalstrike</title>
+ <link rel="match" href="menclose-5-verticalstrike-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').setAttribute("notation","verticalstrike");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose"><mspace width="100px" height="50px"></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-actuarial.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-actuarial.html
new file mode 100644
index 0000000000..61eee24010
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-actuarial.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose actuarial</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="actuarial"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-bottom.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-bottom.html
new file mode 100644
index 0000000000..af473d04c3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-bottom.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose bottom</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="bottom"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-box.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-box.html
new file mode 100644
index 0000000000..1cee2eb15b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-box.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose box</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="box"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-circle.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-circle.html
new file mode 100644
index 0000000000..933e4b55c6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-circle.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose circle</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="circle"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-downdiagonalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-downdiagonalstrike.html
new file mode 100644
index 0000000000..8ec61c14cc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-downdiagonalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose downdiagonalstrike</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="downdiagonalstrike"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-horizontalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-horizontalstrike.html
new file mode 100644
index 0000000000..5b5387ae22
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-horizontalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose horizontalstrike</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="horizontalstrike"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-left.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-left.html
new file mode 100644
index 0000000000..33bdef7ebc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-left.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose left</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="left"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-longdiv.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-longdiv.html
new file mode 100644
index 0000000000..95d71ce4c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-longdiv.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose longdiv</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="longdiv"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-madruwb.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-madruwb.html
new file mode 100644
index 0000000000..6129e2f027
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-madruwb.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose madruwb</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="madruwb"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-phasorangle.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-phasorangle.html
new file mode 100644
index 0000000000..2069642661
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-phasorangle.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose phasorangle</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math>
+ <menclose id="testMenclose" notation="phasorangle">
+ <mspace width="100px" height="50px" mathbackground="blue"></mspace>
+ </menclose>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-ref.html
new file mode 100644
index 0000000000..d8fbb7e3d9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>menclose 6 reference</title>
+ </head>
+ <body>
+ <p>
+ <math><menclose><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-right.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-right.html
new file mode 100644
index 0000000000..8da33e29e2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-right.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose right</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="right"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-roundedbox.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-roundedbox.html
new file mode 100644
index 0000000000..3564769d9a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-roundedbox.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose roundedbox</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="roundedbox"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-top.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-top.html
new file mode 100644
index 0000000000..17fdaa14af
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-top.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose top</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="top"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalarrow.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalarrow.html
new file mode 100644
index 0000000000..22c8c1163d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalarrow.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalarrow</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="updiagonalarrow"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalstrike.html
new file mode 100644
index 0000000000..38d614b742
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-updiagonalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose updiagonalstrike</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="updiagonalstrike"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-verticalstrike.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-verticalstrike.html
new file mode 100644
index 0000000000..d44df0857c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-6-verticalstrike.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>menclose verticalstrike</title>
+ <link rel="match" href="menclose-6-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('testMenclose').removeAttribute("notation");
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <p>
+ <math><menclose id="testMenclose" notation="verticalstrike"><mspace width="100px" height="50px" mathbackground="blue"></mspace></menclose></math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir-ref.html
new file mode 100644
index 0000000000..8a1c908c14
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir-ref.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+ <head><title>Test dir=rtl</title></head>
+ <body>
+ <p>
+ menclose:
+ <math>
+ <menclose>
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="left":
+ <math>
+ <menclose notation="left">
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="right":
+ <math>
+ <menclose notation="right">
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="updiagonalstrike":
+ <math>
+ <menclose notation="updiagonalstrike">
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="downdiagonalstrike":
+ <math>
+ <menclose notation="downdiagonalstrike">
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ </menclose>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir.html
new file mode 100644
index 0000000000..c344e73107
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-dir.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test dir=rtl</title>
+ <link rel="match" href="menclose-dir-ref.html"/>
+ </head>
+ <body>
+ <p>
+ menclose:
+ <math dir="rtl">
+ <menclose>
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="left":
+ <math dir="rtl">
+ <menclose notation="left">
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="right":
+ <math dir="rtl">
+ <menclose notation="right">
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="updiagonalstrike":
+ <math dir="rtl">
+ <menclose notation="updiagonalstrike">
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ </menclose>
+ </math>
+ </p>
+
+ <p>
+ menclose notation="downdiagonalstrike":
+ <math dir="rtl">
+ <menclose notation="downdiagonalstrike">
+ <mspace width="25px" height="25px" mathbackground="red"/>
+ <mspace width="25px" height="25px" mathbackground="green"/>
+ <mspace width="25px" height="25px" mathbackground="blue"/>
+ </menclose>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/menclose-in-mphantom-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-in-mphantom-ref.html
new file mode 100644
index 0000000000..2cc362f91a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/menclose-in-mphantom-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mphantom</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+
+ <!-- <mphantom> should be invisible. -->
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation-ref.html b/testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation-ref.html
new file mode 100644
index 0000000000..b3bfa6a077
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>notation attribute on mstyle (reference)</title>
+ </head>
+ <body>
+ <math>
+ <mstyle>
+ <menclose>
+ <mtext>menclose</mtext>
+ </menclose>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation.html b/testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation.html
new file mode 100644
index 0000000000..c74f0075a0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/menclose/mstyle-notation.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>notation attribute on mstyle</title>
+ <link rel="match" href="mstyle-notation-ref.html"/>
+ <meta name="assert" content="notation attribute on mstyle does not apply to menclose descendants.">
+ </head>
+ <body>
+ <math>
+ <mstyle notation="box">
+ <menclose>
+ <mtext>menclose</mtext>
+ </menclose>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic-ref.html b/testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic-ref.html
new file mode 100644
index 0000000000..566089f93f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mo@accent</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <math>
+ <munderover><mtext>X</mtext><mo accent="true">O</mo><mo>O</mo></munderover>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic.html b/testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic.html
new file mode 100644
index 0000000000..4a5eca4c25
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mo-accent/mo-accent-dynamic.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>mo@accent</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="mo-accent-dynamic-ref.html"/>
+ <script type="text/javascript">
+ function doTest() {
+ document.getElementById('mathOperator').setAttribute('accent', 'true');
+ document.documentElement.removeAttribute("class");
+ }
+ window.addEventListener("TestRendered",doTest);
+ </script>
+ </head>
+ <body>
+ <math>
+ <munderover><mtext>X</mtext><mo id="mathOperator">O</mo><mo>O</mo></munderover>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10-ref.html b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10-ref.html
new file mode 100644
index 0000000000..8026c82489
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>op-dict mo accent</title>
+</head>
+<body>
+ <math>
+ <munderover><mtext>X</mtext><mo accent="true">&#x2192;</mo><mo>O</mo></munderover>
+ </math>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10.html b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10.html
new file mode 100644
index 0000000000..67217592ca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-10.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>op-dict mo accent</title>
+ <link rel="match" href="op-dict-10-ref.html"/>
+</head>
+<body>
+ <math>
+ <munderover><mtext>X</mtext><mo>&#x2192;</mo><mo>O</mo></munderover>
+ </math>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11-ref.html b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11-ref.html
new file mode 100644
index 0000000000..8026c82489
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>op-dict mo accent</title>
+</head>
+<body>
+ <math>
+ <munderover><mtext>X</mtext><mo accent="true">&#x2192;</mo><mo>O</mo></munderover>
+ </math>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11.html b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11.html
new file mode 100644
index 0000000000..d2b93644ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mo-accent/op-dict-11.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>op-dict mo accent</title>
+ <link rel="mismatch" href="op-dict-11-ref.html"/>
+</head>
+<body>
+ <math>
+ <munderover><mtext>X</mtext><mo accent="false">&#x2192;</mo><mo>O</mo></munderover>
+ </math>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001-ref.html b/testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001-ref.html
new file mode 100644
index 0000000000..0b6efcbafc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ </head>
+ <body>
+ <math>
+ <mpadded mathbackground="blue" width="100px" height="3height" depth="3depth"
+ lspace="0" voffset="10px">
+ <mpadded mathbackground="red" width="20px" height="10px"
+ depth="10px">
+ </mpadded>
+ </mpadded>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001.html b/testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001.html
new file mode 100644
index 0000000000..80ba835109
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/mpadded/mpadded-seudo-units-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ <meta name="assert" content="Verify basic rendering of mpadded with pseudo units.">
+ <link rel="match" href="mpadded-seudo-units-001-ref.html">
+ </head>
+ <body>
+ <math>
+ <!-- if lspace is used as a pseudounit, the lspace value is 0 -->
+ <mpadded mathbackground="blue" width="100px" height="3height" depth="3depth"
+ lspace="3lspace" voffset="10px">
+ <mpadded mathbackground="red" width="20px" height="10px"
+ depth="10px">
+ </mpadded>
+ </mpadded>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001-ref.html b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001-ref.html
new file mode 100644
index 0000000000..8ec9adf808
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ <style type="text/css">
+ div#square1 {
+ position:absolute;
+ width: 20px;
+ height: 20px;
+ left: -25px;
+ top: 25px;
+ background: red;
+ }
+ div#square2 {
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ left: 40px;
+ top:30px;
+ background: blue;
+ }
+
+ div#square3 {
+ position:absolute;
+ width: 200px;
+ height: 200px;
+ background: green;
+ }
+ /* left = lspace = -25;
+ top = HeightBig - HeightSmall - voffsetSmall
+ = 50 - 10 - 15 = 25px */
+ /* left = lsapce = 40px;
+ top = HeightVeryBig - HeightBig - voffsetBig
+ = 100 - 50 - 20 = 30px */
+ </style>
+ </head>
+ <body>
+ <div id="square3">
+ <div id="square2">
+ <div id="square1">
+ </div>
+ </div>
+ </div>
+ </body>
+<html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001.html b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001.html
new file mode 100644
index 0000000000..0842d97eb9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-001.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ <meta name="assert" content="Verify negative mpadded@lspace.">
+ <link rel="match" href="mpadded-negative-attributes-001-ref.html">
+ </head>
+ <body>
+ <math>
+ <mpadded mathbackground="green" width="200px" height="100px" depth="100px"
+ lspace="40px" voffset="20px">
+ <mpadded mathbackground="blue" width="100px" height="50px" depth="50px"
+ lspace="-25px" voffset="15px">
+ <mpadded mathbackground="red" width="20px" height="10px"
+ depth="10px">
+ </mpadded>
+ </mpadded>
+ </mpadded>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002-ref.html b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002-ref.html
new file mode 100644
index 0000000000..31ae7f2491
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ </head>
+ <body>
+ <math>
+ <mpadded mathbackground="blue" width="100px" height="100px" depth="0px"
+ lspace="20px" voffset="10px">
+ <mpadded mathbackground="red" width="20px" height="10px"
+ depth="10px">
+ </mpadded>
+ </mpadded>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002.html b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002.html
new file mode 100644
index 0000000000..b2de08e811
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/mpadded-negative-attributes-002.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test mpadded</title>
+ <meta name="assert" content="Verify negative mpadded@width/height/depth.">
+ <link rel="match" href="mpadded-negative-attributes-002-ref.html">
+ </head>
+ <body>
+ <math>
+ <!--if height, depth or width is a negative value, 0px is the value applied -->
+ <mpadded mathbackground="blue" width="100px" height="100px" depth="-100px"
+ lspace="20px" voffset="10px">
+ <mpadded mathbackground="red" width="20px" height="10px"
+ depth="10px">
+ </mpadded>
+ </mpadded>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001-ref.xhtml b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001-ref.xhtml
new file mode 100644
index 0000000000..86af23a887
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001-ref.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mn>3</mn>
+ <mo>+</mo>
+ <mn>2</mn>
+ </math>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001.xhtml b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001.xhtml
new file mode 100644
index 0000000000..600b955a27
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-lspace-rspace-001.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>negative lspace/rspace</title>
+ <link rel="match" href="negative-lspace-rspace-001-ref.xhtml"/>
+ <link rel="assert" href="Verify that negative lspace/rspace are ignored."/>
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=411227"/>
+ <link rel="help" href="https://github.com/w3c/mathml-core/issues/132"/>
+ </head>
+ <body>
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mn>3</mn>
+ <mo lspace="-5pt" rspace="-4pt">+</mo>
+ <mn>2</mn>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1-ref.html b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1-ref.html
new file mode 100644
index 0000000000..ccf1ed995b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>negative mspace</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+ <body>
+ <p>
+ <math>
+ <mrow>
+ <mspace width="1em" height="1em" mathbackground="red"></mspace>
+ <mspace width="1em" height="1em" mathbackground="blue"></mspace>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mrow>
+ <mspace width="1em" height="1em" mathbackground="blue"></mspace>
+ <mspace width="1em" height="1em" mathbackground="red"></mspace>
+ </mrow>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1.html b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1.html
new file mode 100644
index 0000000000..403a98b409
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/negative-lengths/negative-mspace-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>negative mspace</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="negative-mspace-1-ref.html"/>
+ <link rel="assert" href="Verify that negative width on an mspace element causes overlap of previous and next siblings."/>
+ <link rel="help" href="https://github.com/w3c/mathml-core/issues/132"/>
+ </head>
+ <body>
+ <p>
+ <math>
+ <mrow>
+ <mspace width="2em" height="1em" mathbackground="red"></mspace>
+ <mspace width="-1em"/>
+ <mspace width="1em" height="1em" mathbackground="blue"></mspace>
+ </mrow>
+ </math>
+ </p>
+ <p>
+ <math dir="rtl">
+ <mrow>
+ <mspace width="2em" height="1em" mathbackground="red"></mspace>
+ <mspace width="-1em"/>
+ <mspace width="1em" height="1em" mathbackground="blue"></mspace>
+ </mrow>
+ </math>
+ </p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1-ref.html
new file mode 100644
index 0000000000..e5ea01a7de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1-ref.html
@@ -0,0 +1,77 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable>
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1a.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1a.html
new file mode 100644
index 0000000000..c642df9ebb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1a.html
@@ -0,0 +1,78 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="columnlines-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable columnlines="none">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1b.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1b.html
new file mode 100644
index 0000000000..291f3e229a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1b.html
@@ -0,0 +1,78 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="columnlines-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable columnlines="solid">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1c.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1c.html
new file mode 100644
index 0000000000..b6bdc39342
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-1c.html
@@ -0,0 +1,78 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="columnlines-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable columnlines="dashed">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2-ref.html
new file mode 100644
index 0000000000..84c96c5fe3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2-ref.html
@@ -0,0 +1,41 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable columnlines="solid none dashed dashed dashed">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>*</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2a.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2a.html
new file mode 100644
index 0000000000..9b4b681b36
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2a.html
@@ -0,0 +1,42 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="columnlines-2-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable columnlines="solid none dashed">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>*</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2b.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2b.html
new file mode 100644
index 0000000000..db595557dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-2b.html
@@ -0,0 +1,42 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="columnlines-2-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable columnlines="solid none dashed dashed dashed solid solid none solid none">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>*</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1-ref.html
new file mode 100644
index 0000000000..ce49f5dad4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1-ref.html
@@ -0,0 +1,32 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable columnlines="dashed">
+ <mtr>
+ <mtd><mspace height="150px"></mspace></mtd>
+ <mtd><mspace height="150px"></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 115px; left: 0px;
+ width: 50px; height: 55px; background: red;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1.html
new file mode 100644
index 0000000000..d919933c21
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-1.html
@@ -0,0 +1,49 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="columnlines-3-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <!-- We assume there is a gap of more than 1px between dashes. Hence
+ putting two dashed lines with only one pixel of difference in their
+ vertical coordinates should not render the same as one dashed line
+ alone. The opposite is true for continuous line.
+ The red squares allow to ignore the difference at the bounds -->
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable columnlines="dashed">
+ <mtr>
+ <mtd><mspace height="150px"></mspace></mtd>
+ <mtd><mspace height="150px"></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 6px; left: 5px;">
+ <math>
+ <mtable columnlines="dashed">
+ <mtr>
+ <mtd><mspace height="150px"></mspace></mtd>
+ <mtd><mspace height="150px"></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 115px; left: 0px;
+ width: 50px; height: 55px; background: red;"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2-ref.html
new file mode 100644
index 0000000000..6549ad5263
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2-ref.html
@@ -0,0 +1,32 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable columnlines="solid">
+ <mtr>
+ <mtd><mspace height="150px"></mspace></mtd>
+ <mtd><mspace height="150px"></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 115px; left: 0px;
+ width: 50px; height: 55px; background: red;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2.html b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2.html
new file mode 100644
index 0000000000..e95de7c581
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/columnlines-3-2.html
@@ -0,0 +1,49 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="columnlines-3-2-ref.html"/>
+ </head>
+
+ <body>
+
+ <!-- We assume there is a gap of more than 1px between dashes. Hence
+ putting two dashed lines with only one pixel of difference in their
+ vertical coordinates should not render the same as one dashed line
+ alone. The opposite is true for continuous line.
+ The red squares allow to ignore the difference at the bounds -->
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable columnlines="solid">
+ <mtr>
+ <mtd><mspace height="150px"></mspace></mtd>
+ <mtd><mspace height="150px"></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 6px; left: 5px;">
+ <math>
+ <mtable columnlines="solid">
+ <mtr>
+ <mtd><mspace height="150px"></mspace></mtd>
+ <mtd><mspace height="150px"></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 115px; left: 0px;
+ width: 50px; height: 55px; background: red;"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/dir-6a-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/dir-6a-ref.html
new file mode 100644
index 0000000000..d66b9c2710
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/dir-6a-ref.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+ <head><title>Test dir=rtl</title></head>
+ <body>
+
+ <p>
+ mtable:
+ <math>
+ <mtable rowspacing="">
+ <mtr>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+
+ <p>
+ mtable:
+ <math>
+ <mtable frame="solid">
+ <mtr>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/dir-6a.html b/testing/web-platform/mozilla/tests/mathml/tables/dir-6a.html
new file mode 100644
index 0000000000..d71d289cf0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/dir-6a.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test dir=rtl</title>
+ <link rel="match" href="dir-6a-ref.html"/>
+ </head>
+ <body>
+
+ <p>
+ mtable:
+ <math dir="rtl">
+ <mtable rowspacing="">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+
+ <p>
+ mtable:
+ <math dir="rtl">
+ <mtable frame="solid">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mstyle-align-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mstyle-align-ref.html
new file mode 100644
index 0000000000..800ba5b2d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mstyle-align-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>align attribute on mstyle/math</title>
+<meta name="assert" content="align attribute on mstyle/math does not apply to mtable descendants." />
+<body>
+ <math align="baseline">
+ <mstyle align="baseline">
+ <mrow>
+ <mtext>_</mtext>
+ <mtable>
+ <mtr>
+ <mtd>
+ <mtext>&#x2015;</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>&#x2015;</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ <mtext>_</mtext>
+ </mrow>
+ </mstyle>
+ </math>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mstyle-align.html b/testing/web-platform/mozilla/tests/mathml/tables/mstyle-align.html
new file mode 100644
index 0000000000..396dbc599c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mstyle-align.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>align attribute on mstyle/math</title>
+<link rel="match" href="mstyle-align-ref.html" />
+<meta name="assert" content="align attribute on mstyle/math does not apply to mtable descendants." />
+<body>
+ <math align="baseline">
+ <mstyle align="baseline">
+ <mrow>
+ <mtext>_</mtext>
+ <mtable>
+ <mtr>
+ <mtd>
+ <mtext>&#x2015;</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>&#x2015;</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ <mtext>_</mtext>
+ </mrow>
+ </mstyle>
+ </math>
+</body>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2-ref.html
new file mode 100644
index 0000000000..0d14017e54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<head>
+<title>mtable align attribute: negative rownumber</title>
+</head>
+
+<body>
+
+<div>
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow>
+<mtable align="axis 0"><mtr><mtd><mi>&nbsp;</mi></mtd></mtr> <mtr><mtd><mi>&nbsp;</mi></mtd></mtr> <mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr></mtable>
+<mo>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</mo>
+<mtable align="axis 0"><mtr><mtd><mi>&nbsp;</mi></mtd></mtr> <mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr></mtable>
+<mo>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</mo>
+<mtable align="axis 0"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr> <mtr><mtd><mi>g</mi></mtd> <mtd><mi>h</mi></mtd> <mtd><mi>i</mi></mtd></mtr></mtable>
+</mrow>
+</math>
+</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2.html
new file mode 100644
index 0000000000..a3e5b1c7cf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-2.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<head>
+<title>mtable align attribute: negative rownumber</title>
+<link rel="match" href="mtable-align-negative-rownumber-2-ref.html"/>
+</head>
+
+<body>
+
+<div>
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow>
+<mtable align="axis -1"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr></mtable>
+<mo>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</mo>
+<mtable align="axis -1"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr></mtable>
+<mo>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</mo>
+<mtable align="axis -1"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr> <mtr><mtd><mi>g</mi></mtd> <mtd><mi>h</mi></mtd> <mtd><mi>i</mi></mtd></mtr></mtable>
+</mrow>
+</math>
+</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-ref.html
new file mode 100644
index 0000000000..ebf3bf8780
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<head>
+<title>mtable align attribute: negative rownumber</title>
+</head>
+
+<body>
+
+<div>
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mrow>
+<mtable align="axis 3"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr> <mtr><mtd><mi>g</mi></mtd> <mtd><mi>h</mi></mtd> <mtd><mi>i</mi></mtd></mtr></mtable><mo>=</mo>
+<mtable align="axis 1"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr> <mtr><mtd><mi>g</mi></mtd> <mtd><mi>h</mi></mtd> <mtd><mi>i</mi></mtd></mtr></mtable></mrow></math>
+</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber.html
new file mode 100644
index 0000000000..dc654045ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-negative-rownumber.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<head>
+<title>mtable align attribute: negative rownumber</title>
+<link rel="match" href="mtable-align-negative-rownumber-ref.html"/>
+</head>
+
+<body>
+
+<div>
+<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mrow>
+<mtable align="axis -1"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr> <mtr><mtd><mi>g</mi></mtd> <mtd><mi>h</mi></mtd> <mtd><mi>i</mi></mtd></mtr></mtable><mo>=</mo>
+<mtable align="axis 1"><mtr><mtd><mi>a</mi></mtd> <mtd><mi>b</mi></mtd> <mtd><mi>c</mi></mtd></mtr> <mtr><mtd><mi>d</mi></mtd> <mtd><mi>e</mi></mtd> <mtd><mi>f</mi></mtd></mtr> <mtr><mtd><mi>g</mi></mtd> <mtd><mi>h</mi></mtd> <mtd><mi>i</mi></mtd></mtr></mtable></mrow></math>
+</div>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace-ref.html
new file mode 100644
index 0000000000..df39badc67
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace-ref.html
@@ -0,0 +1,342 @@
+<!DOCTYPE html>
+<head>
+<title>mtable align : WhiteSpace Check</title>
+ <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+ <style>
+ math {
+ font: 25px Ahem;
+ }
+ </style>
+</head>
+
+<body>
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+<br><br><br>
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+
+
+
+</body>
+
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace.html
new file mode 100644
index 0000000000..47d29e233a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-align-whitespace.html
@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<head>
+<title>mtable align : WhiteSpace Check</title>
+ <link rel="match" href="mtable-align-whitespace-ref.html"/>
+ <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
+ <style>
+ math {
+ font: 25px Ahem;
+ }
+ </style>
+</head>
+
+<body>
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center-3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center-3 ">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align=" center-3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align=" center-3 ">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align=" center -3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center -3 ">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align=" center -3 ">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+
+<br><br><br>
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="&#xA&#xD;&#x20;center&#xA&#xD;&#x20;-3&#xA&#xD;&#x9;">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="&#xA&#xD;&#x9;&#x20;center&#xA&#xD;&#x9;&#x20;-3&#xA&#xD;&#x9;&#x20;">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center-3&#xA&#xD;&#x9;&#x20;">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="&#xA&#xD;&#x9;&#x20;center-3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="&#xA&#xD;&#x9;&#x20;center-3&#xA&#xD;&#x9;&#x20;">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="&#xA&#xD;&#x9;&#x20;center&#xA&#xD;&#x9;&#x20;-3">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+
+ <math>
+
+ <mtext>X</mtext>
+ <mo>=</mo>
+ <mtable frame="solid" align="center&#xA&#xD;&#x9;&#x20;-3&#xA&#xD;&#x9;&#x20;">
+ <mtr>
+ <mtd><mtext>A</mtext></mtd>
+ <mtd><mtext>B</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>C</mtext></mtd>
+ <mtd><mtext>D</mtext></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mtext>E</mtext></mtd>
+ <mtd><mtext>F</mtext></mtd>
+ </mtr>
+ </mtable>
+
+ </math>
+
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable-dynamic.html
new file mode 100644
index 0000000000..a2e6509732
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable-dynamic.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-multi-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+
+ // Table 1 setup
+ table1.setAttribute("columnalign", "left center right");
+ // Table 2 setup
+ table2.setAttribute("columnalign", "right left center");
+ // Table 3 setup
+ table3.setAttribute("columnalign", "center right left");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>columnalign="left center right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right left center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center right left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable.html
new file mode 100644
index 0000000000..eb188cbe8f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtable.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-multi-ref.html"/>
+ </head>
+ <body>
+ <b>columnalign="left center right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnalign="left center right">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right left center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnalign="right left center">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center right left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnalign="center right left">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr-dynamic.html
new file mode 100644
index 0000000000..8ed1fa7962
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr-dynamic.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-multi-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+ var rows = undefined;
+
+ // Table 1 setup
+ rows = table1.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("columnalign", "left center right");
+
+ // Table 2 setup
+ rows = table2.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("columnalign", "right left center");
+
+ // Table 3 setup
+ rows = table3.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("columnalign", "center right left");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>columnalign="left center right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right left center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center right left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr.html
new file mode 100644
index 0000000000..0c77587be6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-mtr.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-multi-ref.html"/>
+ </head>
+ <body>
+ <b>columnalign="left center right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr columnalign="left center right">
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="left center right">
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="left center right">
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right left center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr columnalign="right left center">
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="right left center">
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="right left center">
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center right left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr columnalign="center right left">
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="center right left">
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="center right left">
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-ref.html
new file mode 100644
index 0000000000..538eb2ad11
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-multi-ref.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnalign</title>
+ </head>
+ <body>
+ <b>columnalign="left center right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd columnalign="left">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="left">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="left">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right left center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd columnalign="right">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="right">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="right">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center right left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd columnalign="center">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="center">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="center">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable-dynamic.html
new file mode 100644
index 0000000000..eefd2b8bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable-dynamic.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-single-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+
+ // Table 1 setup
+ table1.setAttribute("columnalign", "left");
+ // Table 2 setup
+ table2.setAttribute("columnalign", "center");
+ // Table 3 setup
+ table3.setAttribute("columnalign", "right");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>columnalign="left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable.html
new file mode 100644
index 0000000000..6bc72ef689
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtable.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-single-ref.html"/>
+ </head>
+ <body>
+ <b>columnalign="left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnalign="left">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnalign="center">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnalign="right">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr-dynamic.html
new file mode 100644
index 0000000000..bc82954d4a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr-dynamic.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-single-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+ var rows = undefined;
+
+ // Table 1 setup
+ rows = table1.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("columnalign", "left");
+
+ // Table 2 setup
+ rows = table2.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("columnalign", "center");
+
+ // Table 3 setup
+ rows = table3.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("columnalign", "right");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>columnalign="left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr.html
new file mode 100644
index 0000000000..0b52880bbf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-mtr.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnalign</title>
+ <link rel="match" href="mtable-columnalign-single-ref.html"/>
+ </head>
+ <body>
+ <b>columnalign="left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr columnalign="left">
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="left">
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="left">
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr columnalign="center">
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="center">
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="center">
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr columnalign="right">
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ <mtd>
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="right">
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd>
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr columnalign="right">
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-ref.html
new file mode 100644
index 0000000000..69e7627f5b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnalign-single-ref.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnalign</title>
+ </head>
+ <body>
+ <b>columnalign="left"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd columnalign="left">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="left">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="left">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="left">
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd columnalign="center">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="center">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="center">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="center">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnalign="right"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd columnalign="right">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>123</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>123</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="right">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>12345</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>12345</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd columnalign="right">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd columnalign="right">
+ <mtext>1234567</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1234567</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-mtable-dynamic.html
new file mode 100644
index 0000000000..c44f54a04d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-mtable-dynamic.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML columnlines</title>
+ <link rel="match" href="mtable-columnlines-multi-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+
+ // Table 1 setup
+ table1.setAttribute("columnlines", "solid dashed");
+ // Table 2 setup
+ table2.setAttribute("columnlines", "dashed solid");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>columnlines="solid dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnlines="dashed solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-ref.html
new file mode 100644
index 0000000000..f3ec936817
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-multi-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnlines</title>
+ </head>
+ <body>
+ <b>columnlines="solid dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnlines="solid dashed">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnlines="dashed solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnlines="dashed solid">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-mtable-dynamic.html
new file mode 100644
index 0000000000..cc94d94179
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-mtable-dynamic.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML columnlines</title>
+ <link rel="match" href="mtable-columnlines-single-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+
+ // Table 1 setup
+ table1.setAttribute("columnlines", "solid");
+ // Table 2 setup
+ table2.setAttribute("columnlines", "dashed");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>columnlines="solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnlines="dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-ref.html
new file mode 100644
index 0000000000..d6fc000c77
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-columnlines-single-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML columnlines</title>
+ </head>
+ <body>
+ <b>columnlines="solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnlines="solid">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>columnlines="dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable columnlines="dashed">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable-dynamic.html
new file mode 100644
index 0000000000..f8ebe79999
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable-dynamic.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML rowalign</title>
+ <link rel="match" href="mtable-rowalign-multi-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+
+ // Table 1 setup
+ table1.setAttribute("rowalign", "top center bottom");
+ // Table 2 setup
+ table2.setAttribute("rowalign", "bottom top center");
+ // Table 3 setup
+ table3.setAttribute("rowalign", "center bottom top");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>rowalign="top center bottom"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom top center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center bottom top"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable.html
new file mode 100644
index 0000000000..de8cd62e94
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-mtable.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowalign</title>
+ <link rel="match" href="mtable-rowalign-multi-ref.html"/>
+ </head>
+ <body>
+ <b>rowalign="top center bottom"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable rowalign="top center bottom">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom top center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowalign="bottom top center">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center bottom top"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowalign="center bottom top">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-ref.html
new file mode 100644
index 0000000000..6e1fc1910a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-multi-ref.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowalign</title>
+ </head>
+ <body>
+ <b>rowalign="top center bottom"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom top center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center bottom top"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable-dynamic.html
new file mode 100644
index 0000000000..c0c66c8979
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable-dynamic.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML rowalign</title>
+ <link rel="match" href="mtable-rowalign-single-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+
+ // Table 1 setup
+ table1.setAttribute("rowalign", "top");
+ // Table 2 setup
+ table2.setAttribute("rowalign", "center");
+ // Table 3 setup
+ table3.setAttribute("rowalign", "bottom");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>rowalign="top"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable.html
new file mode 100644
index 0000000000..2ecb4ee001
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtable.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowalign</title>
+ <link rel="match" href="mtable-rowalign-single-ref.html"/>
+ </head>
+ <body>
+ <b>rowalign="top"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable rowalign="top">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowalign="center">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowalign="bottom">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr-dynamic.html
new file mode 100644
index 0000000000..cb298c8047
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr-dynamic.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML rowalign</title>
+ <link rel="match" href="mtable-rowalign-single-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+ var table3 = document.getElementById("table3");
+ var rows = undefined;
+
+ // Table 1 setup
+ rows = table1.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("rowalign", "top");
+
+ // Table 2 setup
+ rows = table2.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("rowalign", "center");
+
+ // Table 3 setup
+ rows = table3.getElementsByTagName("mtr");
+ for(var i = 0; i < rows.length; i++)
+ rows[i].setAttribute("rowalign", "bottom");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>rowalign="top"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table3">
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr.html
new file mode 100644
index 0000000000..7481d29768
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-mtr.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowalign</title>
+ <link rel="match" href="mtable-rowalign-single-ref.html"/>
+ </head>
+ <body>
+ <b>rowalign="top"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr rowalign="top">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr rowalign="top">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr rowalign="top">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr rowalign="center">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr rowalign="center">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr rowalign="center">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr rowalign="bottom">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr rowalign="bottom">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr rowalign="bottom">
+ <mtd>
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-ref.html
new file mode 100644
index 0000000000..b343cb7b8e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowalign-single-ref.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowalign</title>
+ </head>
+ <body>
+ <b>rowalign="top"</b> <br/>
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="top">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="center"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="center">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowalign="bottom"</b> <br />
+ <math>
+ <mstyle>
+ <mtable>
+ <mtr>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="5px" height="5px" mathbackground="red"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="10px" height="10px" mathbackground="green"></mspace>
+ </mtd>
+ <mtd rowalign="bottom">
+ <mspace width="10px" depth="15px" height="15px" mathbackground="blue"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-mtable-dynamic.html
new file mode 100644
index 0000000000..e00fcd3504
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-mtable-dynamic.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML rowlines</title>
+ <link rel="match" href="mtable-rowlines-multi-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+
+ // Table 1 setup
+ table1.setAttribute("rowlines", "solid dashed");
+ // Table 2 setup
+ table2.setAttribute("rowlines", "dashed solid");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>rowlines="solid dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowlines="dashed solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-ref.html
new file mode 100644
index 0000000000..ac5f1f1c2b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-multi-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowlines</title>
+ </head>
+ <body>
+ <b>rowlines="solid dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowlines="solid dashed">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowlines="dashed solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowlines="dashed solid">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-mtable-dynamic.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-mtable-dynamic.html
new file mode 100644
index 0000000000..da283aaab9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-mtable-dynamic.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Test MathML rowlines</title>
+ <link rel="match" href="mtable-rowlines-single-ref.html"/>
+ <script type="text/javascript">
+ function doTest()
+ {
+ var table1 = document.getElementById("table1");
+ var table2 = document.getElementById("table2");
+
+ // Table 1 setup
+ table1.setAttribute("rowlines", "solid");
+ // Table 2 setup
+ table2.setAttribute("rowlines", "dashed");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </head>
+ <body>
+ <b>rowlines="solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table1">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowlines="dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable id="table2">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-ref.html
new file mode 100644
index 0000000000..000eaa2a24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-rowlines-single-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test MathML rowlines</title>
+ </head>
+ <body>
+ <b>rowlines="solid"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowlines="solid">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ <b>rowlines="dashed"</b> <br />
+ <math>
+ <mstyle>
+ <mtable rowlines="dashed">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </mstyle>
+ </math> <br />
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-width-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-width-ref.html
new file mode 100644
index 0000000000..d93f5a60b4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-width-ref.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+<div>
+ <math>
+ <mtext>|</mtext>
+ <mtable style="width:8em">
+ <mtr>
+ <mtd></mtd>
+ </mtr>
+ </mtable>
+ <mtext>|</mtext>
+ </math>
+</div>
+<div>
+ <math>
+ <mtext>|</mtext>
+ <mtable style="width:30px">
+ <mtr>
+ <mtd></mtd>
+ </mtr>
+ </mtable>
+ <mtext>|</mtext>
+ </math>
+</div>
+<div>
+ <math>
+ <mtext>|</mtext>
+ <mtable>
+ <mtr>
+ <mtd></mtd>
+ </mtr>
+ </mtable>
+ <mtext>|</mtext>
+ </math>
+</div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/mtable-width.html b/testing/web-platform/mozilla/tests/mathml/tables/mtable-width.html
new file mode 100644
index 0000000000..e4e705c2e6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/mtable-width.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Check that mtable supports the width attribute</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="mtable-width-ref.html"/>
+ </head>
+
+ <body>
+
+<div>
+ <math>
+ <mtext>|</mtext>
+ <mtable width="8em">
+ <mtr>
+ <mtd></mtd>
+ </mtr>
+ </mtable>
+ <mtext>|</mtext>
+ </math>
+</div>
+<div>
+ <math>
+ <mtext>|</mtext>
+ <mtable width="30px">
+ <mtr>
+ <mtd></mtd>
+ </mtr>
+ </mtable>
+ <mtext>|</mtext>
+ </math>
+</div>
+<div>
+ <math>
+ <mtext>|</mtext>
+ <mtable width="auto">
+ <mtr>
+ <mtd></mtd>
+ </mtr>
+ </mtable>
+ <mtext>|</mtext>
+ </math>
+</div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1-ref.html
new file mode 100644
index 0000000000..e5ea01a7de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1-ref.html
@@ -0,0 +1,77 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable>
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1a.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1a.html
new file mode 100644
index 0000000000..973f25191c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1a.html
@@ -0,0 +1,78 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="rowlines-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable rowlines="none">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1b.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1b.html
new file mode 100644
index 0000000000..d4db5d3a97
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1b.html
@@ -0,0 +1,78 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="rowlines-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable rowlines="solid">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1c.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1c.html
new file mode 100644
index 0000000000..04488fc214
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-1c.html
@@ -0,0 +1,78 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="rowlines-1-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable rowlines="dashed">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ <mtd>
+ <mn>6</mn>
+ </mtd>
+ <mtd>
+ <mn>7</mn>
+ </mtd>
+ <mtd>
+ <mn>8</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>9</mn>
+ </mtd>
+ <mtd>
+ <mn>10</mn>
+ </mtd>
+ <mtd>
+ <mn>11</mn>
+ </mtd>
+ <mtd>
+ <mn>12</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>13</mn>
+ </mtd>
+ <mtd>
+ <mn>14</mn>
+ </mtd>
+ <mtd>
+ <mn>15</mn>
+ </mtd>
+ <mtd>
+ <mn>16</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2-ref.html
new file mode 100644
index 0000000000..f66962bad6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2-ref.html
@@ -0,0 +1,51 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable rowlines="none dashed solid solid solid">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>*</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2a.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2a.html
new file mode 100644
index 0000000000..a4d73a2f55
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2a.html
@@ -0,0 +1,52 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="rowlines-2-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable rowlines="none dashed solid">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>*</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2b.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2b.html
new file mode 100644
index 0000000000..2ac9ea8fab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-2b.html
@@ -0,0 +1,52 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="rowlines-2-ref.html"/>
+ </head>
+
+ <body>
+
+ <math>
+ <mtable rowlines="none dashed solid solid solid none dashed dashed none">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>2</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>3</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>4</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>5</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>*</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1-ref.html
new file mode 100644
index 0000000000..04dac8cca4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1-ref.html
@@ -0,0 +1,40 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ div, math {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ </head>
+
+ <body>
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable width="150px" rowlines="dashed">
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 5px; left: 110px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1.html
new file mode 100644
index 0000000000..5d259cdbd3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-1.html
@@ -0,0 +1,60 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="mismatch" href="rowlines-3-1-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ div, math {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ </head>
+
+ <body>
+
+ <!-- We assume there is a gap of more than 1px between dashes. Hence
+ putting two dashed lines with only one pixel of difference in their
+ horizontal coordinates should not render the same as one dashed line
+ alone. The opposite is true for continuous line.
+ The red squares allow to ignore the difference at the bounds -->
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable width="150px" rowlines="dashed">
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 6px;">
+ <math>
+ <mtable width="150px" rowlines="dashed">
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 5px; left: 110px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2-ref.html
new file mode 100644
index 0000000000..3375c87c9b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2-ref.html
@@ -0,0 +1,40 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ div, math {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ </head>
+
+ <body>
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable width="150px" rowlines="solid">
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 5px; left: 112px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2.html b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2.html
new file mode 100644
index 0000000000..8b390a06d0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-3-2.html
@@ -0,0 +1,60 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>mtable frame</title>
+ <!-- Copyright (c) 2011 Design Science, Inc.
+ License: Apache License 2.0 -->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="match" href="rowlines-3-2-ref.html"/>
+ <link rel="stylesheet" href="/fonts/ahem.css" />
+ <style>
+ div, math {
+ font: 25px/1 Ahem;
+ }
+ </style>
+ </head>
+
+ <body>
+
+ <!-- We assume there is a gap of more than 1px between dashes. Hence
+ putting two dashed lines with only one pixel of difference in their
+ horizontal coordinates should not render the same as one dashed line
+ alone. The opposite is true for continuous line.
+ The red squares allow to ignore the difference at the bounds -->
+
+ <div style="position: absolute; top: 5px; left: 5px;">
+ <math>
+ <mtable width="150px" rowlines="solid">
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 6px;">
+ <math>
+ <mtable width="150px" rowlines="solid">
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mspace></mspace></mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </div>
+
+ <div style="position: absolute; top: 5px; left: 0px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ <div style="position: absolute; top: 5px; left: 112px;
+ width: 50px; height: 50px; background: red;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496-ref.xhtml b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496-ref.xhtml
new file mode 100644
index 0000000000..88aaad25f3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496-ref.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body>
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mfenced open="[" close="]">
+ <mtable id="table" rowlines="dashed">
+ <mtr>
+ <mtd><mi>x</mi></mtd>
+ <mtd><mi>y</mi></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mi>z</mi></mtd>
+ <mtd><mi>w</mi></mtd>
+ </mtr>
+ </mtable>
+ </mfenced>
+</math></div>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496.xhtml b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496.xhtml
new file mode 100644
index 0000000000..2b5fc03ff9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/rowlines-dynamic-mozilla-347496.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="match" href="rowlines-dynamic-mozilla-347496-ref.xhtml"/>
+ </head>
+
+<body onload="document.getElementById('table').setAttribute('rowlines', 'dashed')">
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mfenced open="[" close="]">
+ <mtable id="table">
+ <mtr>
+ <mtd><mi>x</mi></mtd>
+ <mtd><mi>y</mi></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mi>z</mi></mtd>
+ <mtd><mi>w</mi></mtd>
+ </mtr>
+ </mtable>
+ </mfenced>
+</math></div>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/semantics-4-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/semantics-4-ref.html
new file mode 100644
index 0000000000..92d3236c27
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/semantics-4-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
+ <mtable width="100%" mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+</math>
+
+<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
+ <mtable mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+</math>
+
+<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML">
+ <mtable width="100%" mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+</math>
+
+<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML">
+ <mtable mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+</math>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/semantics-4.html b/testing/web-platform/mozilla/tests/mathml/tables/semantics-4.html
new file mode 100644
index 0000000000..bdd13a9c24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/semantics-4.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<head>
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1131000"/>
+ <link rel="match" href="semantics-4-ref.html"/>
+</head>
+
+<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
+ <semantics>
+ <mtable width="100%" mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+ <annotation encoding="application/x-tex">y</annotation>
+ </semantics>
+</math>
+
+<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
+ <semantics>
+ <mtable mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+ <annotation encoding="application/x-tex">y</annotation>
+ </semantics>
+</math>
+
+<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML">
+ <semantics>
+ <mtable width="100%" mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+ <annotation encoding="application/x-tex">y</annotation>
+ </semantics>
+</math>
+
+<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML">
+ <semantics>
+ <mtable mathbackground="red">
+ <mtr> <mtd><mi>x</mi></mtd> </mtr>
+ </mtable>
+ <annotation encoding="application/x-tex">y</annotation>
+ </semantics>
+</math>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/spacing-attributes-001.html b/testing/web-platform/mozilla/tests/mathml/tables/spacing-attributes-001.html
new file mode 100644
index 0000000000..08e2d63ecd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/spacing-attributes-001.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test framespacing/rowspacing/columnspacing attributes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=330964"/>
+</head>
+<body>
+
+<math>
+ <mtable framespacing="7px 20px" frame="solid" rowspacing="11px 27px" columnspacing="5px 16px"
+ style="border-width: 2px;" id="mtable0">
+ <mtr>
+ <mtd id="mtd0">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd1">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd2">
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd id="mtd3">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd4">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd5">
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd id="mtd6">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd7">
+ <mn>X</mn>
+ </mtd>
+ <mtd id="mtd8">
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+</math>
+
+<script type="application/javascript">
+
+ var epsilon = 2;
+
+ rectTable = document.getElementById("mtable0").getBoundingClientRect();
+ rect0 = document.getElementById("mtd0").getBoundingClientRect();
+ rect1 = document.getElementById("mtd1").getBoundingClientRect();
+ rect2 = document.getElementById("mtd2").getBoundingClientRect();
+ rect3 = document.getElementById("mtd3").getBoundingClientRect();
+ rect4 = document.getElementById("mtd4").getBoundingClientRect();
+ rect5 = document.getElementById("mtd5").getBoundingClientRect();
+ rect6 = document.getElementById("mtd6").getBoundingClientRect();
+ rect7 = document.getElementById("mtd7").getBoundingClientRect();
+ rect8 = document.getElementById("mtd8").getBoundingClientRect();
+ test(function() {
+ assert_approx_equals(rect1.left - rect0.right, 5, epsilon);
+ assert_approx_equals(rect2.left - rect1.right, 16, epsilon);
+ assert_approx_equals(rect4.left - rect3.right, 5, epsilon);
+ assert_approx_equals(rect5.left - rect4.right, 16, epsilon);
+ assert_approx_equals(rect7.left - rect6.right, 5, epsilon);
+ assert_approx_equals(rect8.left - rect7.right, 16, epsilon);
+ }, "columnspacing");
+ test(function() {
+ assert_approx_equals(rect3.top - rect0.bottom, 11, epsilon);
+ assert_approx_equals(rect4.top - rect1.bottom, 11, epsilon);
+ assert_approx_equals(rect5.top - rect2.bottom, 11, epsilon);
+ assert_approx_equals(rect6.top - rect3.bottom, 27, epsilon);
+ assert_approx_equals(rect7.top - rect4.bottom, 27, epsilon);
+ assert_approx_equals(rect8.top - rect5.bottom, 27, epsilon);
+ }, "rowspacing");
+ // Remember to subtract border
+ test(function() {
+ assert_approx_equals(rect0.left - rectTable.left - 2, 7, epsilon);
+ assert_approx_equals(rect3.left - rectTable.left - 2, 7, epsilon);
+ assert_approx_equals(rect6.left - rectTable.left - 2, 7, epsilon);
+ }, "framespacing left");
+ test(function() {
+ assert_approx_equals(rect0.top - rectTable.top - 2, 20, epsilon);
+ assert_approx_equals(rect1.top - rectTable.top - 2, 20, epsilon);
+ assert_approx_equals(rect2.top - rectTable.top - 2, 20, epsilon);
+ }, "framespacing top");
+ test(function() {
+ assert_approx_equals(rectTable.bottom - rect6.bottom - 2, 20, epsilon);
+ assert_approx_equals(rectTable.bottom - rect7.bottom - 2, 20, epsilon);
+ assert_approx_equals(rectTable.bottom - rect8.bottom - 2, 20, epsilon);
+ }, "framespacing bottom");
+ test(function() {
+ assert_approx_equals(rectTable.right - rect2.right - 2, 7, epsilon);
+ assert_approx_equals(rectTable.right - rect5.right - 2, 7, epsilon);
+ assert_approx_equals(rectTable.right - rect8.right - 2, 7, epsilon);
+ }, "framespacing right");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1-ref.html
new file mode 100644
index 0000000000..3ad0881e22
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1-ref.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+ <head><title>Table spacing default values</title></head>
+ <body>
+
+ <p>
+ <math>
+ <mtable rowspacing="1.0ex" columnspacing="0.8em" framespacing="0em 0ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math >
+ <mtable frame="solid" rowspacing="1.0ex" columnspacing="0.8em" framespacing="0.4em 0.5ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1.html
new file mode 100644
index 0000000000..0259e6dea2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-1.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Table spacing default values</title>
+ <link rel="match" href="tablespacing-1-ref.html"/>
+ </head>
+ <body>
+
+ <p>
+ <math>
+ <mtable rowspacing="">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mtable frame="solid" rowspacing="">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2-ref.html
new file mode 100644
index 0000000000..0bf2e4822a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2-ref.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html>
+ <head><title>Additional values ignored and insufficient ones expanded</title></head>
+ <body>
+
+ <p>
+ <math>
+ <mtable rowspacing="1.0ex 3.0ex 7.0ex" columnspacing="0.8em 2em 3em" framespacing="0em 0ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mtable rowspacing="1.0ex 3.0ex 3.0ex" columnspacing="0.8em 2em 2em">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2.html
new file mode 100644
index 0000000000..e5b2aa33a2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-2.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Additional values ignored and insufficient ones expanded</title>
+ <link rel="match" href="tablespacing-2-ref.html"/>
+ </head>
+ <body>
+
+ <p>
+ <math>
+ <mtable rowspacing="1.0ex 3.0ex 7.0ex 10ex 12ex" columnspacing="0.8em 2em 3em 5em 7em" framespacing="6em 9ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mtable rowspacing="1.0ex 3.0ex" columnspacing="0.8em 2em">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3-ref.html
new file mode 100644
index 0000000000..f4180d8dfb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3-ref.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+ <head><title>Single value for rowspacing/columnspacing accepted</title></head>
+ <body>
+
+ <p>
+ <math>
+ <mtable columnspacing="4em 4em 4em">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mtable rowspacing="4.0ex 4.0ex 4.0ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3.html
new file mode 100644
index 0000000000..65a4bf5839
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-3.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Single value for rowspacing/columnspacing accepted</title>
+ <link rel="match" href="tablespacing-3-ref.html"/>
+ </head>
+ <body>
+
+ <p>
+ <math>
+ <mtable columnspacing="4em">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math>
+ <mtable rowspacing="4.0ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ <mtd>
+ <mtext>g</mtext>
+ </mtd>
+ <mtd>
+ <mtext>h</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>i</mtext>
+ </mtd>
+ <mtd>
+ <mtext>j</mtext>
+ </mtd>
+ <mtd>
+ <mtext>k</mtext>
+ </mtd>
+ <mtd>
+ <mtext>l</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>m</mtext>
+ </mtd>
+ <mtd>
+ <mtext>n</mtext>
+ </mtd>
+ <mtd>
+ <mtext>o</mtext>
+ </mtd>
+ <mtd>
+ <mtext>p</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4-ref.html
new file mode 100644
index 0000000000..42474859e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4-ref.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dynamic tests involving adding and removing elements</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <math>
+ <mtable columnspacing="5em 7em">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable columnspacing="5em">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable rowspacing="2ex 4ex">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable rowspacing="2ex">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable rowspacing="2ex 4ex" columnspacing="4em">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ <mtd>
+ <mn>w</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4.html
new file mode 100644
index 0000000000..25d27b4479
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-4.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Dynamic tests involving adding and removing elements</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="tablespacing-4-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mtable columnspacing="5em 7em" id="mtable0a">
+ <mtr id="mtr0">
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable columnspacing="5em 7em">
+ <mtr id="mtr0a">
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ <mtd id="mtd0">
+ <mn>y</mn>
+ </mtd>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable rowspacing="2ex 4ex" id="mtable0">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable rowspacing="2ex 4ex" id="mtable1">
+ <mtr>
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ </mtr>
+ <mtr id="mtr1">
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable rowspacing="2ex 4ex" columnspacing="4em">
+ <mtr id="mtr2">
+ <mtd>
+ <mn>x</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>y</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>z</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <script>
+ function doTest() {
+ // Add a table cell
+ var mn0 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mn");
+ mn0.innerHTML = 'z';
+ var mtd0 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtd");
+ mtd0.appendChild(mn0);
+ document.getElementById("mtr0").appendChild(mtd0);
+ // Remove a table cell
+ document.getElementById("mtr0a").removeChild(document.getElementById("mtd0"));
+ // Add a table row
+ var mn1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mn");
+ mn1.innerHTML = 'z';
+ var mtd1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtd");
+ mtd1.appendChild(mn1);
+ var mtr1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtr");
+ mtr1.appendChild(mtd1);
+ document.getElementById("mtable0").appendChild(mtr1);
+ // Remove a table row
+ document.getElementById("mtable1").removeChild(document.getElementById("mtr1"));
+ // Add a table cell to a table containing several rows
+ var mn2 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mn");
+ mn2.innerHTML = 'w';
+ var mtd2 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mtd");
+ mtd2.appendChild(mn2);
+ document.getElementById("mtr2").appendChild(mtd2);
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5-ref.html
new file mode 100644
index 0000000000..c79ccd1e88
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5-ref.html
@@ -0,0 +1,274 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dynamic tests involving changes to row/column/framespacing</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <math>
+ <mtable id="mtable0" >
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable1">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable2" frame="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable3" rowspacing="2ex 4ex">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable4" columnspacing="2em 3em" >
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable5" framespacing="2em 2ex" frame="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable6">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5.html
new file mode 100644
index 0000000000..61595de845
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5.html
@@ -0,0 +1,296 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Dynamic tests involving changes to row/column/framespacing</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="tablespacing-5-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mtable id="mtable0" rowspacing="2ex 3ex">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable1" columnspacing="2em 3em">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable2" framespacing="2em 2ex" frame="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable3" >
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable4" >
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable5" frame="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable6" frame="none">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <script>
+ function doTest() {
+ // remove spacing attributes
+ document.getElementById("mtable0").removeAttribute("rowspacing");
+ document.getElementById("mtable1").removeAttribute("columnspacing");
+ document.getElementById("mtable2").removeAttribute("framespacing");
+
+ // add spacing attributes
+ document.getElementById("mtable3").setAttribute("rowspacing", "2ex 4ex");
+ document.getElementById("mtable4").setAttribute("columnspacing", "2em 3em");
+ document.getElementById("mtable5").setAttribute("framespacing", "2em 2ex");
+
+ // framespacing doesn't apply with frame="none"
+ document.getElementById("mtable6").setAttribute("framespacing", "2em 2ex");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a-ref.html
new file mode 100644
index 0000000000..4d0820805c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a-ref.html
@@ -0,0 +1,278 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dynamic tests involving changes to row/column/framespacing</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <!-- There are at present two implementations of mtable spacing behaviour,
+ one based on CSS and the other on rowspacing/columnspacing/framespacing
+ attributes which the user can select. This reftest tests that dynamic
+ transitions from one system to the other work appropriately. -->
+ <math>
+ <mtable id="mtable0" columnspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable1" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable2" frame="solid" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable3" rowspacing="2ex 4ex" columspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable4" columnspacing="2em 3em" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable5" framespacing="2em 2ex" frame="solid" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable6" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a.html
new file mode 100644
index 0000000000..8269e599b3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-5a.html
@@ -0,0 +1,296 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Dynamic tests involving changes to row/column/framespacing</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="tablespacing-5a-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mtable id="mtable0" rowspacing="2ex 3ex" columnspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable1" columnspacing="2em 3em" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable2" framespacing="2em 2ex" frame="solid" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable3" columspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable4" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable5" frame="solid" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable6" frame="none" rowspacing="">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <script>
+ function doTest() {
+ // remove spacing attributes
+ document.getElementById("mtable0").removeAttribute("rowspacing");
+ document.getElementById("mtable1").removeAttribute("columnspacing");
+ document.getElementById("mtable2").removeAttribute("framespacing");
+
+ // add spacing attributes
+ document.getElementById("mtable3").setAttribute("rowspacing", "2ex 4ex");
+ document.getElementById("mtable4").setAttribute("columnspacing", "2em 3em");
+ document.getElementById("mtable5").setAttribute("framespacing", "2em 2ex");
+
+ // framespacing doesn't apply with frame="none"
+ document.getElementById("mtable6").setAttribute("framespacing", "2em 2ex");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6-ref.html
new file mode 100644
index 0000000000..a08f5a0ca5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6-ref.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Dynamic tests involving changes to row/column/framespacing</title>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <math>
+ <mtable id="mtable0" rowspacing="3ex 7ex">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable1" columnspacing="3em 7em">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable2" framespacing="3em 7ex" frame="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6.html
new file mode 100644
index 0000000000..96fa362494
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-6.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <title>Dynamic tests involving changes to row/column/framespacing</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="tablespacing-6-ref.html"/>
+ </head>
+ <body>
+ <math>
+ <mtable id="mtable0" rowspacing="2ex 3ex">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable1" columnspacing="2em 3em">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <math>
+ <mtable id="mtable2" framespacing="2em 2ex" frame="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ <p>
+ <script>
+ function doTest() {
+
+ // modify existing spacing attributes
+ document.getElementById("mtable0").setAttribute("rowspacing", "3ex 7ex");
+ document.getElementById("mtable1").setAttribute("columnspacing", "3em 7em");
+ document.getElementById("mtable2").setAttribute("framespacing", "3em 7ex");
+
+ document.documentElement.removeAttribute("class");
+ }
+ document.documentElement.addEventListener("TestRendered", doTest);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7-ref.html
new file mode 100644
index 0000000000..ad2d53b03e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7-ref.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html>
+ <head><title>Table spacing error handling</title></head>
+ <body>
+
+ <p>
+ <math>
+ <mtable frame="solid" rowspacing="5.0ex 1.0ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math >
+ <mtable frame="solid" columnspacing="5em 0.8em">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ <p>
+ <math >
+ <mtable frame="solid" framespacing="0.4em 0.5ex">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7.html
new file mode 100644
index 0000000000..5d51bfd3e2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-7.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Table spacing error handling</title>
+ <link rel="match" href="tablespacing-7-ref.html"/>
+ </head>
+ <body>
+
+ <p>
+ <math>
+ <mtable frame="solid" rowspacing="5.0ex cat">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+ </p>
+ <p>
+ <math >
+ <mtable frame="solid" columnspacing="5em cat">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ <p>
+ <math >
+ <mtable frame="solid" framespacing="0.4em cat">
+ <mtr>
+ <mtd>
+ <mtext>a</mtext>
+ </mtd>
+ <mtd>
+ <mtext>b</mtext>
+ </mtd>
+ <mtd>
+ <mtext>c</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>d</mtext>
+ </mtd>
+ <mtd>
+ <mtext>e</mtext>
+ </mtd>
+ <mtd>
+ <mtext>f</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+
+ </p>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8-ref.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8-ref.html
new file mode 100644
index 0000000000..a39c15b3d6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8-ref.html
@@ -0,0 +1,38 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <body>
+
+ <math>
+ <mtable rowspacing="30px" columnspacing="30px">
+ <mtr>
+ <mtd>
+ <mspace width="50px" height="30px"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="50px" height="30px"></mspace>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mspace width="50px" height="30px"></mspace>
+ </mtd>
+ <mtd>
+ <mspace width="50px" height="30px"></mspace>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+ <div style="position: absolute; top: 0; left: 0px;
+ width: 150px; height: 40px; background: black;"></div>
+ <div style="position: absolute; top: 70px; left: 0px;
+ width: 150px; height: 40px; background: black;"></div>
+ <div style="position: absolute; top: 0px; left: 0px;
+ width: 60px; height: 100px; background: black;"></div>
+ <div style="position: absolute; top: 0px; left: 90px;
+ width: 60px; height: 100px; background: black;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8a.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8a.html
new file mode 100644
index 0000000000..9ed7adf31d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8a.html
@@ -0,0 +1,42 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="mismatch" href="tablespacing-8-ref.html"/>
+ </head>
+ <body>
+
+ <math>
+ <mtable rowspacing="30px" columnspacing="60px"
+ rowlines="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+ <div style="position: absolute; top: 0; left: 0px;
+ width: 150px; height: 40px; background: black;"></div>
+ <div style="position: absolute; top: 70px; left: 0px;
+ width: 150px; height: 40px; background: black;"></div>
+ <div style="position: absolute; top: 0px; left: 0px;
+ width: 60px; height: 100px; background: black;"></div>
+ <div style="position: absolute; top: 0px; left: 90px;
+ width: 60px; height: 100px; background: black;"></div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8b.html b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8b.html
new file mode 100644
index 0000000000..ff01ce9e73
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/tables/tablespacing-8b.html
@@ -0,0 +1,41 @@
+<!-- -*- mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set tabstop=2 expandtab shiftwidth=2 textwidth=80: -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="mismatch" href="tablespacing-8-ref.html"/>
+ </head>
+ <body>
+
+ <math>
+ <mtable rowspacing="20px" columnspacing="100px"
+ columnlines="solid">
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ <mtd>
+ <mn>X</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+ <div style="position: absolute; top: 0; left: 0px;
+ width: 150px; height: 40px; background: black;"></div>
+ <div style="position: absolute; top: 70px; left: 0px;
+ width: 150px; height: 40px; background: black;"></div>
+ <div style="position: absolute; top: 0px; left: 0px;
+ width: 60px; height: 100px; background: black;"></div>
+ <div style="position: absolute; top: 0px; left: 90px;
+ width: 60px; height: 100px; background: black;"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3-ref.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3-ref.html
new file mode 100644
index 0000000000..6d82b4ba0d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>table-width-3</title>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ html { background-color: grey; }
+ td { border: 1px solid white;
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-right: 1px;
+ padding-left: 1px;
+ background-color: black;
+ color: red; }
+ mi, mtext { font-size: 3em; }
+ span { font-style: italic; display: inline-block; }
+ </style>
+ </head>
+ <body>
+ <table>
+ <tr>
+ <td>
+ <math>
+ <mphantom>
+ <mi>f</mi>
+ </mphantom>
+ </math>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3.html
new file mode 100644
index 0000000000..f5211e3cad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/table-width-3.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>table-width-3</title>
+ <meta charset="utf-8"/>
+ <link rel="match" href="table-width-3-ref.html"/>
+ <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=415413"/>
+ <style type="text/css">
+ html { background-color: grey; }
+ td { border: 1px solid white;
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-right: 1px;
+ padding-left: 1px;
+ background-color: black;
+ color: black; }
+ mi, mtext { font-size: 3em; }
+ span { font-style: italic; display: inline-block; }
+ </style>
+ </head>
+ <body>
+ <table>
+ <tr>
+ <td>
+ <math>
+ <mi> f </mi>
+ </math>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1-ref.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1-ref.html
new file mode 100644
index 0000000000..b2145dedf0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<math><mo minsize="10em">(</mo></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1.html
new file mode 100644
index 0000000000..c7a63dd131
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-1.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<link rel="match" href="whitespace-trim-1-ref.html"/>
+<math><mo minsize="10em"> ( </mo></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2-ref.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2-ref.html
new file mode 100644
index 0000000000..8b8f7b8129
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<math><mi>(</mi></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2.html
new file mode 100644
index 0000000000..db84b1207d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-2.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<link rel="match" href="whitespace-trim-2-ref.html"/>
+<math><mi> ( </mi></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3-ref.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3-ref.html
new file mode 100644
index 0000000000..b8ba67d8f0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<math><mi>&#x210e;</mi></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3.html
new file mode 100644
index 0000000000..d26dddfcad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-3.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<link rel="match" href="whitespace-trim-3-ref.html"/>
+<math><mi> &#x210e; </mi></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4-ref.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4-ref.html
new file mode 100644
index 0000000000..f5951467cb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<math><ms>x</ms></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4.html
new file mode 100644
index 0000000000..8a93d94b15
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-4.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<link rel="match" href="whitespace-trim-4-ref.html"/>
+<math><ms> x </ms></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5-ref.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5-ref.html
new file mode 100644
index 0000000000..560259c86f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<math><mtext>|</mtext><mi>x</mi><mtext>|</mtext></math>
+<math><mtext>|</mtext><mn>x</mn><mtext>|</mtext></math>
+<math><mtext>|</mtext><mo>x</mo><mtext>|</mtext></math>
+<math><mtext>|</mtext><mtext>x</mtext><mtext>|</mtext></math>
diff --git a/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5.html b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5.html
new file mode 100644
index 0000000000..48f62691a1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mathml/whitespace-trimming/whitespace-trim-5.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<link rel="match" href="whitespace-trim-5-ref.html"/>
+<math><mtext>|</mtext><mi> x </mi><mtext>|</mtext></math>
+<math><mtext>|</mtext><mn> x </mn><mtext>|</mtext></math>
+<math><mtext>|</mtext><mo> x </mo><mtext>|</mtext></math>
+<math><mtext>|</mtext><mtext> x </mtext><mtext>|</mtext></math>
diff --git a/testing/web-platform/mozilla/tests/media/2x2-green.ogv b/testing/web-platform/mozilla/tests/media/2x2-green.ogv
new file mode 100644
index 0000000000..29903c0a81
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/media/2x2-green.ogv
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html
new file mode 100644
index 0000000000..e7dcd4e531
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<html>
+<head>
+<title>enumerateDevices: test that enumerateDevices is present (legacy Firefox)</title>
+<meta name='assert' content='Check that the enumerateDevices() method is present (legacy Firefox).'/>
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This is a modified copy of
+testing/web-platform/tests/mediacapture-streams/MediaDevices-enumerateDevices.https.html
+testing legacy Firefox version of the <code>navigator.mediaDevices.enumerateDevices()</code> method.</p>
+<div id='log'></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src=permission-helper.js></script>
+<script>
+"use strict";
+
+promise_test(async () => {
+ assert_not_equals(navigator.mediaDevices.enumerateDevices, undefined, "navigator.mediaDevices.enumerateDevices exists");
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ for (const {kind, deviceId, label, groupId} of devices) {
+ assert_in_array(kind, ["videoinput", "audioinput", "audiooutput"]);
+ assert_greater_than(deviceId.length, 0, "deviceId should be present even if getUserMedia was never called successfully (legacy).");
+ assert_equals(label, "", "label should be empty string if getUserMedia was never called successfully.");
+ assert_greater_than(groupId.length, 0, "groupId should be present even if getUserMedia was never called successfully (legacy).");
+ }
+ assert_less_than_equal(devices.filter(({kind}) => kind == "audioinput").length,
+ 1, "there should be zero or one audio input device.");
+ assert_less_than_equal(devices.filter(({kind}) => kind == "videoinput").length,
+ 1, "there should be zero or one video input device.");
+ assert_equals(devices.filter(({kind}) => kind == "audiooutput").length,
+ 0, "there should be no audio output devices.");
+ assert_less_than_equal(devices.length, 2,
+ "there should be no more than two devices.");
+ if (devices.length > 1) {
+ assert_equals(devices[0].kind, "audioinput", "audioinput is first");
+ assert_equals(devices[1].kind, "videoinput", "videoinput is second");
+ }
+}, "mediaDevices.enumerateDevices() is present and working - before capture");
+
+promise_test(async t => {
+ await setMediaPermission("granted");
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+ stream.getTracks()[0].stop();
+
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ const kinds = ["audioinput", "videoinput"];
+ for (const {kind, deviceId} of devices) {
+ assert_in_array(kind, kinds, "camera doesn't expose audiooutput");
+ assert_equals(typeof deviceId, "string", "deviceId is a string.");
+ switch (kind) {
+ case "videoinput":
+ assert_greater_than(deviceId.length, 0, "video deviceId should not be empty.");
+ break;
+ case "audioinput":
+ assert_greater_than(deviceId.length, 0, "audio deviceId should not be empty (legacy).");
+ break;
+ }
+ }
+}, "mediaDevices.enumerateDevices() is working - after video capture");
+
+// This test is designed to come after its video counterpart directly above
+promise_test(async t => {
+ const devices1 = await navigator.mediaDevices.enumerateDevices();
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ stream.getTracks()[0].stop();
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ assert_equals(devices.filter(({kind}) => kind == "videoinput").length,
+ devices1.filter(({kind}) => kind == "videoinput").length,
+ "same number of (previously exposed) videoinput devices");
+ assert_greater_than_equal(devices.filter(d => d.kind == "audioinput").length,
+ devices1.filter(d => d.kind == "audioinput").length,
+ "same number or more audioinput devices");
+ const order = ["audioinput", "videoinput", "audiooutput"];
+ for (const {kind, deviceId} of devices) {
+ assert_in_array(kind, order, "expected kind");
+ assert_equals(typeof deviceId, "string", "deviceId is a string.");
+ switch (kind) {
+ case "videoinput":
+ assert_greater_than(deviceId.length, 0, "video deviceId should not be empty.");
+ break;
+ case "audioinput":
+ assert_greater_than(deviceId.length, 0, "audio deviceId should not be empty.");
+ break;
+ }
+ }
+ const kinds = devices.map(({kind}) => kind);
+ const correct = [...kinds].sort((a, b) => order.indexOf(a) - order.indexOf(b));
+ assert_equals(JSON.stringify(kinds), JSON.stringify(correct), "correct order");
+}, "mediaDevices.enumerateDevices() is working - after video then audio capture");
+
+promise_test(async () => {
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ for (const mediaInfo of devices) {
+ if (mediaInfo.kind == "audioinput" || mediaInfo.kind == "videoinput") {
+ assert_true("InputDeviceInfo" in window, "InputDeviceInfo exists");
+ assert_true(mediaInfo instanceof InputDeviceInfo);
+ } else if (mediaInfo.kind == "audiooutput") {
+ assert_true(mediaInfo instanceof MediaDeviceInfo);
+ } else {
+ assert_unreached("mediaInfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")
+ }
+ }
+}, "InputDeviceInfo is supported");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html
new file mode 100644
index 0000000000..55d1d24dce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-in-background.https.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<title>enumerateDevices() in background tab with focus in chrome</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body></body>
+<script>
+'use strict';
+// This test is not in cross-browser wpt because it uses Gecko-specific API
+// for focusing browser chrome and it assumes a specific tab browser design.
+// It assumes that
+// * browser chrome widget focus is associated with a single current document
+// presentation (tab),
+// https://github.com/w3c/mediacapture-main/issues/752#issuecomment-742036800
+// * window.open() focuses the new tab and makes the current tab hidden, and
+// * the opener tab becomes the current and visible tab again when the new tab
+// is closed.
+const blank_url = '/common/blank.html';
+
+function promise_event(target, name) {
+ return new Promise(resolve => target[`on${name}`] = resolve);
+}
+
+promise_test(async t => {
+ // Open a new tab, which is expected to receive focus and hide the first tab.
+ await test_driver.bless('window.open()');
+ assert_true(document.hasFocus(), 'This test needs focus on the browser.');
+ const promise_hidden = promise_event(document, 'visibilitychange');
+ const proxy = window.open(blank_url);
+ t.add_cleanup(() => proxy.close());
+ await Promise.all([
+ promise_hidden,
+ promise_event(proxy, 'focus'),
+ promise_event(proxy, 'load'),
+ ]);
+ assert_true(proxy.document.hasFocus(), 'proxy.document.hasFocus()');
+
+ await Promise.all([
+ promise_event(proxy, 'blur'),
+ SpecialPowers.spawnChrome([], function focus_url_bar() {
+ this.browsingContext.topChromeWindow.gURLBar.focus();
+ }),
+ ]);
+ assert_false(proxy.document.hasFocus(), 'proxy.document.hasFocus()');
+ assert_false(document.hasFocus(), 'document.hasFocus()');
+ assert_equals(document.visibilityState, 'hidden', 'visibilityState');
+
+ // Enumeration should remain pending while the first tab is background.
+ const promise_enumerate = navigator.mediaDevices.enumerateDevices();
+ // Enumerate in the foreground tab to confirm that URL bar focus is
+ // sufficient, and to provide enough time to check that the Promise from the
+ // background tab does not settle.
+ await proxy.navigator.mediaDevices.enumerateDevices();
+ // Race a settled Promise to check that the enumeration in the background tab
+ // has not settled.
+ const result = await Promise.race([promise_enumerate, 'pending']);
+ assert_equals(result, 'pending', 'pending Promise while background.');
+
+ // The enumeration Promise should resolve after the first tab returns to the
+ // foreground.
+ proxy.close();
+ await promise_event(document, 'visibilitychange');
+ assert_equals(document.visibilityState, 'visible', 'visibilityState');
+ await promise_enumerate;
+}, 'enumerateDevices in background');
+</script>
diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html
new file mode 100644
index 0000000000..6516a514c5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<title>enumerateDevices() without focus</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body></body>
+<script>
+'use strict';
+const blank_url = '/common/blank.html';
+
+function promise_event(target, name) {
+ return new Promise(resolve => target[`on${name}`] = resolve);
+}
+// When testdriver.js supports switch-to-window, it can replace this function
+// and this test can be upstreamed.
+// https://github.com/web-platform-tests/wpt/issues/10666
+function switch_toplevel_focus_for_window(win) {
+ return win.SpecialPowers.spawnChrome([], function activate_browser_window() {
+ this.browsingContext.topChromeWindow.focus();
+ });
+}
+
+promise_test(async t => {
+ await test_driver.bless('window.open()');
+ assert_true(document.hasFocus(), 'This test needs focus on the document.');
+ const promise_blur = promise_event(window, 'blur');
+ // 'resizable' is requested for a separate OS window on relevant platforms
+ // so that this test tests OS focus changes rather than document visibility.
+ const proxy = window.open(blank_url, '', 'resizable');
+ t.add_cleanup(() => proxy.close());
+ await Promise.all([
+ promise_blur,
+ switch_toplevel_focus_for_window(proxy),
+ promise_event(proxy, 'load'),
+ ]);
+ assert_false(document.hasFocus(), 'document.hasFocus() after blur');
+
+ // Enumeration should remain pending without focus.
+ const promise_enumerate = navigator.mediaDevices.enumerateDevices();
+ // Enumerate in the focused window to provide enough time to check that
+ // the Promise from the unfocused window does not settle.
+ await proxy.navigator.mediaDevices.enumerateDevices();
+ // Race a settled Promise to check that the enumeration in the first window
+ // has not settled.
+ const result = await Promise.race([promise_enumerate, 'pending']);
+ assert_equals(result, 'pending', 'pending Promise without focus.');
+
+ // The enumeration Promise should resolve after focus returns to the window.
+ proxy.close();
+ await Promise.all([
+ promise_event(window, 'focus'),
+ switch_toplevel_focus_for_window(window),
+ ]);
+ assert_true(document.hasFocus(), 'document.hasFocus() after focus');
+ await promise_enumerate;
+}, 'enumerateDevices without focus');
+</script>
diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/permission-helper.js b/testing/web-platform/mozilla/tests/mediacapture-streams/permission-helper.js
new file mode 100644
index 0000000000..0a237f7d43
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/mediacapture-streams/permission-helper.js
@@ -0,0 +1,24 @@
+// Set permissions for camera and microphone using Web Driver
+// Status can be one of "granted" or "denied"
+// Scope take values from permission names
+async function setMediaPermission(status="granted", scope=["camera", "microphone"]) {
+ try {
+ for (let s of scope) {
+ await test_driver.set_permission({ name: s }, status);
+ }
+ } catch (e) {
+ const noSetPermissionSupport = typeof e === "string" && e.match(/set_permission not implemented/);
+ if (!(noSetPermissionSupport ||
+ (e instanceof Error && e.message.match("unimplemented")) )) {
+ throw e;
+ }
+ // Web Driver not implemented action
+ // FF: https://bugzilla.mozilla.org/show_bug.cgi?id=1524074
+
+ // with current WPT runners, will default to granted state for FF and Safari
+ // throw if status!="granted" to invalidate test results
+ if (status === "denied") {
+ assert_implements_optional(!noSetPermissionSupport, "Unable to set permission to denied for this test");
+ }
+ }
+}
diff --git a/testing/web-platform/mozilla/tests/placeholder b/testing/web-platform/mozilla/tests/placeholder
new file mode 100644
index 0000000000..92dd3d5151
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/placeholder
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+This is a placeholder file to ensure that this directory remains
+in source control and test packages even when it is otherwise empty. \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html b/testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html
new file mode 100644
index 0000000000..d0623fbaa5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/screen-capture/getdisplaymedia-user-activation-consumed.https.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<head>
+<title>Test getDisplayMedia() after user activation is consumed</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-screen-share/#dom-mediadevices-getdisplaymedia">
+</head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+ await test_driver.bless('transient activation');
+ // SpecialPowers is used to consume user activation because the only
+ // spec-compliant Gecko API that consumes user activation is
+ // navigator.share(), which is disabled on CI versions of WINNT.
+ // https://searchfox.org/mozilla-central/rev/66547980e8e8ca583473c74f207cae5bac1ed541/testing/web-platform/meta/web-share/share-consume-activation.https.html.ini#4
+ const had_transient_activation =
+ SpecialPowers.wrap(document).consumeTransientUserGestureActivation();
+ assert_true(had_transient_activation,
+ 'should have had transient activation');
+ const p = navigator.mediaDevices.getDisplayMedia();
+ // Race a settled promise to check that the returned promise is already
+ // rejected.
+ await promise_rejects_dom(
+ t, 'InvalidStateError', Promise.race([p, Promise.resolve()]),
+ 'getDisplayMedia should have returned an already-rejected promise.');
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/selection/Selection-addRange-same-instance.html b/testing/web-platform/mozilla/tests/selection/Selection-addRange-same-instance.html
new file mode 100644
index 0000000000..c374b8ecdf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/selection/Selection-addRange-same-instance.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+
+promise_test(async () => {
+ await new Promise(resolve => document.addEventListener("DOMContentLoaded", resolve));
+
+ getSelection().removeAllRanges();
+ const range = document.createRange();
+ range.setStart(document.querySelector("span"), 0);
+ getSelection().addRange(range);
+ getSelection().addRange(range);
+ assert_equals(getSelection().rangeCount, 1, "Adding same collapsed range twice should not clone the range");
+ assert_equals(getSelection().getRangeAt(0), range, "The first range should be the added range");
+ assert_equals(getSelection().focusNode, document.querySelector("span"), "Focus node should be the <span>");
+ assert_equals(getSelection().focusOffset, 0, "Focus offset should be 0");
+ assert_true(getSelection().isCollapsed, "Selection should be collapsed");
+}, "Adding same collapsed range should not change selections");
+
+promise_test(async () => {
+ getSelection().removeAllRanges();
+ const range = document.createRange();
+ range.selectNodeContents(document.querySelector("div"));
+ getSelection().addRange(range);
+ getSelection().addRange(range);
+ assert_equals(getSelection().rangeCount, 1, "Adding same range twice should not clone the range");
+ assert_equals(getSelection().getRangeAt(0), range, "The first range should be the added range");
+ assert_equals(getSelection().anchorNode, document.querySelector("div"), "Anchor node should be the <div>");
+ assert_equals(getSelection().anchorOffset, 0, "Anchor offset should be 0");
+ assert_equals(getSelection().focusNode, document.querySelector("div"), "Focus node should be the <div>");
+ assert_equals(getSelection().focusOffset, 1, "Focus offset should be 1");
+}, "Adding non-collapsed range after updating the range should not change selections");
+
+promise_test(async () => {
+ getSelection().removeAllRanges();
+ const range = document.createRange();
+ range.collapse(document.querySelector("span"), 0);
+ getSelection().addRange(range);
+ range.selectNodeContents(document.querySelector("div"));
+ getSelection().addRange(range);
+ assert_equals(getSelection().rangeCount, 1, "Adding same range twice should not clone the range");
+ assert_equals(getSelection().getRangeAt(0), range, "The first range should be the added range");
+ assert_equals(getSelection().anchorNode, document.querySelector("div"), "Anchor node should be the <div>");
+ assert_equals(getSelection().anchorOffset, 0, "Anchor offset should be 0");
+ assert_equals(getSelection().focusNode, document.querySelector("div"), "Focus node should be the <div>");
+ assert_equals(getSelection().focusOffset, 1, "Focus offset should be 1");
+}, "Adding same collapsed range after updating the range should not change selections");
+</script>
+</head>
+<body><div><span></span></div></body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html b/testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html
new file mode 100644
index 0000000000..e093f616c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/bug1675097.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Controlled iframe with initial about:blank becomes sandboxed</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+// This test creates an iframe controlled by a service worker, which in turn has
+// another iframe in it. The inner iframe's fetch is intercepted by the service
+// worker, which notifies the outer iframe, which then adds the sandbox
+// attribute to the inner iframe. The outer iframe then signals the service
+// worker that it should evaluate self.clients.matchAll() and finally respond to
+// the inner iframe's fetch.
+// Evaluating self.clients.matchAll() causes the creation of the initial
+// about:blank document for the inner iframe, and if the sandboxing flags used
+// for that document are not the original flags for the inner iframe (i.e. none)
+// then the document will have an opaque origin, a case that we need to handle
+// properly.
+promise_test(async t => {
+ const URL = 'resources/bug1675097-sw.js';
+ const SCOPE = 'resources/';
+
+ const registration = await service_worker_unregister_and_register(t, URL, SCOPE);
+ t.add_cleanup(() => registration.unregister());
+
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const outer = await with_iframe(SCOPE + 'bug1675097-iframe.html');
+ t.add_cleanup(() => outer.remove());
+}, 'Regression test for bug 1675097');
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html b/testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html
new file mode 100644
index 0000000000..245522f5fc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/no_intercept_for_crossorigin_media.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Don't intercept cross-origin media requests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js">
+</script>
+<body>
+<script>
+
+'use strict';
+
+const scope = './resources/';
+
+/**
+ * Ensure service workers don't intercept cross-origin media range requests.
+ */
+promise_test(async t => {
+ const registration = await service_worker_unregister_and_register(
+ t, scope + 'intercept_media_sw.js', scope);
+ t.add_cleanup(() => registration.unregister());
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const frame = await with_iframe(scope + 'crossorigin_media_iframe.html');
+ return frame.contentWindow.create_media_promise()
+}, 'Service worker does not intercept a cross-origin media range request');
+
+/**
+ * Ensure service workers do intercept cross-origin media non-range requests.
+ */
+promise_test(async t => {
+ const registration = await service_worker_unregister_and_register(
+ t, scope + 'intercept_media_sw.js', scope);
+ t.add_cleanup(() => registration.unregister());
+ await wait_for_state(t, registration.installing, 'activated');
+
+ const frame = await with_iframe(scope + 'crossorigin_media_iframe_nonrange.html');
+ return frame.contentWindow.create_media_promise()
+}, 'Service worker intercepts a cross-origin non-range media request');
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/blank.html b/testing/web-platform/mozilla/tests/service-workers/resources/blank.html
new file mode 100644
index 0000000000..a3c3a4689a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Empty doc</title>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html
new file mode 100644
index 0000000000..5ad7b95594
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-iframe.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<body>
+<script>
+let channel = new MessageChannel();
+channel.port1.onmessage = event => {
+ if (event.data === 'intercepted') {
+ const iframe = document.querySelector('iframe');
+ iframe.sandbox = '';
+ navigator.serviceWorker.controller.postMessage({ type: 'ack' });
+ }
+};
+navigator.serviceWorker.controller.postMessage({ type: 'register', port: channel.port2 }, [channel.port2]);
+</script>
+<iframe src='inner'></iframe>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js
new file mode 100644
index 0000000000..e2894e1032
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/bug1675097-sw.js
@@ -0,0 +1,26 @@
+// We use promises because the message and fetch events do not have a guaranteed
+// order, since they come from different task sources.
+var resolvePortPromise;
+var portPromise = new Promise(resolve => resolvePortPromise = resolve);
+var resolveResolveResponsePromise;
+var resolveResponsePromise = new Promise(resolve => resolveResolveResponsePromise = resolve);
+
+self.addEventListener('fetch', event => {
+ if (event.request.url.indexOf('inner') !== -1) {
+ event.respondWith(new Promise(resolve => {
+ resolveResolveResponsePromise(resolve);
+ }));
+ portPromise.then(port => port.postMessage('intercepted'));
+ }
+});
+
+self.addEventListener('message', event => {
+ if (event.data.type === 'register') {
+ resolvePortPromise(event.data.port);
+ }
+ else if (event.data.type === 'ack') {
+ self.clients.matchAll()
+ .then(() => resolveResponsePromise)
+ .then(resolveResponse => resolveResponse(new Response('inner iframe')));
+ }
+});
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html
new file mode 100644
index 0000000000..54cdcaa8dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script>
+
+'use strict';
+
+function remoteize(relpath) {
+ let curdir = location.href.replace(/\/[^\/]*$/, '/')
+ return curdir.replace('://', '://www1.') + relpath
+}
+
+function create_media_promise() {
+ return new Promise((resolve, reject) => {
+ let video = document.createElement('video')
+ video.autoplay = true
+ video.muted = true
+ video.onplay = () => resolve('ok')
+ video.onerror = () => reject('video error')
+ video.src = remoteize('fetch_video.py')
+ })
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html
new file mode 100644
index 0000000000..8e3c20fdeb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/crossorigin_media_iframe_nonrange.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<body>
+<script>
+
+'use strict';
+
+function remoteize(relpath) {
+ let curdir = location.href.replace(/\/[^\/]*$/, '/')
+ return curdir.replace('://', '://www1.') + relpath
+}
+
+function create_media_promise() {
+ return new Promise((resolve, reject) => {
+ let image = document.createElement('img')
+ image.onload = () => resolve('ok')
+ image.onerror = () => reject('image error')
+ image.src = remoteize('blank.html') // sw will replace with an image
+ })
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/empty.js b/testing/web-platform/mozilla/tests/service-workers/resources/empty.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/empty.js
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py b/testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py
new file mode 100644
index 0000000000..3945506f30
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/fetch_video.py
@@ -0,0 +1,14 @@
+import os
+
+
+def main(request, response):
+ filename = os.path.join(request.doc_root, "media", "2x2-green.ogv")
+ body = open(filename, "rb").read()
+ length = len(body)
+ headers = [
+ (b"Content-Type", b"video/ogg"),
+ (b"Accept-Ranges", b"bytes"),
+ (b"Content-Length", b"%d" % length),
+ (b"Content-Range", b"bytes 0-%d/%d" % (length - 1, length)),
+ ]
+ return headers, body
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/green.png b/testing/web-platform/mozilla/tests/service-workers/resources/green.png
new file mode 100644
index 0000000000..28a1faab37
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/green.png
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js b/testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js
new file mode 100644
index 0000000000..6c373aa0de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/resources/intercept_media_sw.js
@@ -0,0 +1,14 @@
+'use strict';
+
+self.addEventListener('fetch', event => {
+ if (event.request.url.indexOf('fetch_video.py') !== -1) {
+ // A no-cors media range request /should not/ be intercepted.
+ // Respond with some text to cause an error.
+ event.respondWith(new Response('intercepted'));
+ }
+ else if (event.request.url.indexOf('blank.html') !== -1) {
+ // A no-cors media non-range request /should/ be intercepted.
+ // Respond with an image to avoid an error.
+ event.respondWith(fetch('green.png'));
+ }
+});
diff --git a/testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html b/testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html
new file mode 100644
index 0000000000..371fa81161
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/service-workers/update_completes_in_disconnected_global.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Service Worker: Disconnected Global Update()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+/**
+ * Gecko-only pseudo-crash-test to verify that if we call
+ * ServiceWorkerRegistration.update() from a global that will be destroyed
+ * before any IPC call could return that we don't crash.
+ *
+ * We use an iframe to create a global that we can destroy on demand.
+ */
+promise_test(async function(t) {
+ const scope = 'resources/blank.html';
+ const sw_url = 'resources/empty.js';
+
+ let reg = await service_worker_unregister_and_register(t, sw_url, scope);
+ t.add_cleanup(function() {
+ return service_worker_unregister(t, scope);
+ });
+
+ // Wait for the worker to be activated so that we are in a known state.
+ await wait_for_state(t, reg.installing, 'activated');
+
+ let f = await with_iframe(scope);
+ let f_global = f.contentWindow;
+ // The frame should be controlled, although it's not necessary for the test.
+ assert_true(!!f_global.navigator.serviceWorker.controller);
+ t.add_cleanup(function() {
+ if (f) {
+ f.remove();
+ }
+ });
+
+ // Get a registration object that lives in the iframe's global.
+ let f_reg = await f_global.navigator.serviceWorker.getRegistration(reg.scope);
+ assert_true(!!f_reg, 'got registration');
+ assert_equals(reg.scope, f_reg.scope, 'Right registration');
+
+ // Trigger the update and destroy the global.
+ let update_resolved = false;
+ let update_rejected = false;
+
+ let update_promise = f_reg.update();
+ update_promise.then(
+ () => { update_resolved = true; }, () => { update_rejected = true; });
+
+ f.remove();
+ f = null;
+ f_global = null;
+
+ // Now we want to wait on an update call that should fire strictly after the
+ // update call above.
+ await reg.update();
+
+ assert_false(update_resolved, "frame update() should not have resolved");
+ assert_true(update_rejected, "frame update() should have rejected");
+}, 'ServiceWorkerRegistration.update() concluding in a disconnected global');
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/svg/smil-sampling.html b/testing/web-platform/mozilla/tests/svg/smil-sampling.html
new file mode 100644
index 0000000000..8eecb63595
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/svg/smil-sampling.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<svg width="100" height="100">
+ <rect width="100" height="100" fill="blue">
+ <animate attributeName="fill" from="brown" to="red" dur="1000s"/>
+ </rect>
+</svg>
+<script>
+function isSampling() {
+ return SpecialPowers.wrap(window).windowUtils.refreshDriverHasPendingTick;
+}
+function tick() {
+ return new Promise(r => {
+ requestAnimationFrame(() => requestAnimationFrame(r));
+ });
+}
+
+// See comment in layout/base/tests/test_bug1756118.html about why the timeouts
+// etc.
+async function expectTicksToStop() {
+ for (let i = 0; i < 100; i++) {
+ await new Promise(r => setTimeout(r, 8));
+ if(!isSampling()) {
+ break;
+ }
+ }
+ assert_false(isSampling(), "refresh driver should have eventually stopped ticking");
+}
+
+promise_test(async function(t) {
+ await tick();
+ assert_true(isSampling(), "Animation should be running");
+ let svg = document.querySelector("svg");
+ svg.remove();
+ await tick();
+ await expectTicksToStop();
+
+ document.body.appendChild(svg);
+ await tick();
+ assert_true(isSampling(), "Animation should be running again");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/web-animations/web-animations-no-infinite-refresh.html b/testing/web-platform/mozilla/tests/web-animations/web-animations-no-infinite-refresh.html
new file mode 100644
index 0000000000..3795db841e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/web-animations/web-animations-no-infinite-refresh.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<title>Test that a pending animation doesn't keep vsync running forever</title>
+<body>
+<script>
+function ensureVsyncDisabled() {
+ let wu = SpecialPowers.wrap(window).windowUtils;
+ return new Promise(resolve => {
+ function check() {
+ if (wu.refreshDriverHasPendingTick) {
+ requestIdleCallback(check, {timeout:300});
+ } else {
+ resolve();
+ }
+ }
+ check();
+ })
+}
+
+promise_test(async function() {
+ await ensureVsyncDisabled();
+ let doc = new DOMParser().parseFromString("<div>", "text/html");
+ let anim = doc.querySelector("div").animate(null);
+ assert_true(anim.pending, "Animation should be pending");
+ anim.timeline = document.timeline;
+
+ await ensureVsyncDisabled();
+ assert_true(true, "Vsync should be disabled");
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html b/testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html
new file mode 100644
index 0000000000..539d072d16
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/web-animations/web-animations-print-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<html>
+<title>Web animation</title>
+<p style="color: blue">blue with animation support; olive without</p>
diff --git a/testing/web-platform/mozilla/tests/web-animations/web-animations-print.html b/testing/web-platform/mozilla/tests/web-animations/web-animations-print.html
new file mode 100644
index 0000000000..2b1ed19eb6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/web-animations/web-animations-print.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<link rel="help" href="https://drafts.csswg.org/web-animations/">
+<link rel="match" href="web-animations-print-ref.html">
+<title>Web animation</title>
+<p id="anim" style="color: olive">blue with animation support; olive without</p>
+<script>
+const animationData = [
+ {"color":"#0000FF"},
+ {"color":"#0000FF"}
+];
+
+const animationTiming = {
+ "duration":1,
+ "iterations":Infinity
+};
+
+let element = document.getElementById("anim");
+element.animate(animationData, animationTiming);
+</script>
diff --git a/testing/web-platform/mozilla/tests/webaudio/the-audio-api/the-audioparam-interface/large-exponentialRamp.html b/testing/web-platform/mozilla/tests/webaudio/the-audio-api/the-audioparam-interface/large-exponentialRamp.html
new file mode 100644
index 0000000000..e01036bbc7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webaudio/the-audio-api/the-audioparam-interface/large-exponentialRamp.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Test exponentialRampToValueAtTime() with a large ratio of change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict';
+
+promise_test(async function() {
+ const sampleRate = 16384;
+ // not a power of two, so that there is some rounding error in the exponent
+ const rampEndSample = 255;
+ const bufferSize = rampEndSample + 1;
+ const offset0 = 20.;
+ const offset1 = 20000.;
+ // Math.pow(2, -23) ~ 1 unit in the last place (ulp).
+ // Single-precision powf() amplifies rounding error of less than 0.5 ulp in
+ // to the exponent to more than 2 ulp when the curve spans this large ratio.
+ // This test is not in upstream wpt because this may be more precision than
+ // expected from an implementation.
+ const relativeTolerance = Math.pow(2, -23);
+
+ const context = new OfflineAudioContext(1, bufferSize, sampleRate);
+
+ const source = new ConstantSourceNode(context);
+ source.start();
+ // Explicit event to work around
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1265393
+ source.offset.setValueAtTime(offset0, 0.);
+ source.offset.exponentialRampToValueAtTime(offset1, rampEndSample/sampleRate);
+ source.connect(context.destination);
+
+ const buffer = await context.startRendering();
+ assert_equals(buffer.length, bufferSize, "output buffer length");
+ const output = buffer.getChannelData(0);
+ const ratio = offset1 / offset0;
+ for (let i = 0; i < bufferSize; ++i) {
+ // Math.pow() uses double precision, while `output` has single precision,
+ // but `tolerance` is enough to accommodate differences.
+ const expected = offset0 * Math.pow(offset1/offset0, i/rampEndSample);
+ assert_approx_equals(
+ output[i],
+ expected,
+ relativeTolerance * expected,
+ "scheduled value at " + i);
+ }
+});
+</script>
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browser/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browser/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browser/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/close.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/close.py
new file mode 100644
index 0000000000..a7197a7d0d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browser/close/close.py
@@ -0,0 +1,36 @@
+import pytest
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_close_browser(new_session, add_browser_capabilities):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+
+ await bidi_session.browser.close()
+
+ # Wait for the browser to actually close.
+ bidi_session.current_browser.wait()
+
+ assert bidi_session.current_browser.is_running is False
+
+
+async def test_start_session_again(new_session, add_browser_capabilities):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+ first_session_id = bidi_session.session_id
+
+ await bidi_session.browser.close()
+
+ # Wait for the browser to actually close.
+ bidi_session.current_browser.wait()
+
+ # Try to create a session again.
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+
+ assert isinstance(bidi_session.session_id, str)
+ assert first_session_id != bidi_session.session_id
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py
new file mode 100644
index 0000000000..910b202075
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/__init__.py
@@ -0,0 +1,20 @@
+import contextlib
+
+
+def set_context(session, context):
+ session.send_session_command("POST", "moz/context", {"context": context})
+
+
+@contextlib.contextmanager
+def using_context(session, context):
+ orig_context = session.send_session_command("GET", "moz/context")
+ needs_change = context != orig_context
+
+ if needs_change:
+ set_context(session, context)
+
+ try:
+ yield
+ finally:
+ if needs_change:
+ set_context(session, orig_context)
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py
new file mode 100644
index 0000000000..1a5906339b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/reference_context.py
@@ -0,0 +1,72 @@
+import pytest
+
+from . import using_context
+
+pytestmark = pytest.mark.asyncio
+
+
+# Helper to assert the order of top level browsing contexts.
+# The window used for the assertion is inferred from the first context id of
+# expected_context_ids.
+def assert_tab_order(session, expected_context_ids):
+ with using_context(session, "chrome"):
+ context_ids = session.execute_script(
+ """
+ const contextId = arguments[0];
+ const { TabManager } =
+ ChromeUtils.importESModule("chrome://remote/content/shared/TabManager.sys.mjs");
+ const browsingContext = TabManager.getBrowsingContextById(contextId);
+ const chromeWindow = browsingContext.embedderElement.ownerGlobal;
+ const tabBrowser = TabManager.getTabBrowser(chromeWindow);
+ return tabBrowser.browsers.map(browser => TabManager.getIdForBrowser(browser));
+ """,
+ args=(expected_context_ids[0],),
+ )
+
+ assert context_ids == expected_context_ids
+
+
+async def test_reference_context(bidi_session, current_session):
+ # Create a new window with a tab tab1
+ result = await bidi_session.browsing_context.create(type_hint="window")
+ tab1_context_id = result["context"]
+
+ # Create a second window with a tab tab2
+ result = await bidi_session.browsing_context.create(type_hint="window")
+ tab2_context_id = result["context"]
+
+ # Create a new tab tab3 next to tab1
+ result = await bidi_session.browsing_context.create(
+ type_hint="tab", reference_context=tab1_context_id
+ )
+ tab3_context_id = result["context"]
+
+ # Create a new tab tab4 next to tab2
+ result = await bidi_session.browsing_context.create(
+ type_hint="tab", reference_context=tab2_context_id
+ )
+ tab4_context_id = result["context"]
+
+ # Create a new tab tab5 also next to tab2 (should consequently be between
+ # tab2 and tab4)
+ result = await bidi_session.browsing_context.create(
+ type_hint="tab", reference_context=tab2_context_id
+ )
+ tab5_context_id = result["context"]
+
+ # Create a new window, but pass a reference_context from an existing window.
+ # The reference context is expected to be ignored here.
+ result = await bidi_session.browsing_context.create(
+ type_hint="window", reference_context=tab2_context_id
+ )
+ tab6_context_id = result["context"]
+
+ # We expect 3 windows in total, with a specific tab order:
+ # - the first window should contain tab1, tab3
+ assert_tab_order(current_session, [tab1_context_id, tab3_context_id])
+ # - the second window should contain tab2, tab5, tab4
+ assert_tab_order(
+ current_session, [tab2_context_id, tab5_context_id, tab4_context_id]
+ )
+ # - the third window should contain tab6
+ assert_tab_order(current_session, [tab6_context_id])
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py
new file mode 100644
index 0000000000..337a03b3dd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/create/type_hint.py
@@ -0,0 +1,31 @@
+import pytest
+from tests.support.asserts import assert_success
+
+from . import using_context
+
+pytestmark = pytest.mark.asyncio
+
+
+def count_window_handles(session):
+ with using_context(session, "chrome"):
+ response = session.transport.send(
+ "GET", "session/{session_id}/window/handles".format(**vars(session))
+ )
+ chrome_handles = assert_success(response)
+ return len(chrome_handles)
+
+
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_type_hint(bidi_session, current_session, type_hint):
+ assert len(await bidi_session.browsing_context.get_tree()) == 1
+ assert count_window_handles(current_session) == 1
+
+ await bidi_session.browsing_context.create(type_hint=type_hint)
+
+ if type_hint == "window":
+ expected_window_count = 2
+ else:
+ expected_window_count = 1
+
+ assert len(await bidi_session.browsing_context.get_tree()) == 2
+ assert count_window_handles(current_session) == expected_window_count
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py
new file mode 100644
index 0000000000..374359d1ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/navigate/error.py
@@ -0,0 +1,48 @@
+import os
+from copy import deepcopy
+
+import pytest
+from tests.bidi.browsing_context.navigate import navigate_and_assert
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_insecure_certificate(configuration, url, custom_profile, geckodriver):
+ try:
+ # Create a new profile and remove the certificate storage so that
+ # loading a HTTPS page will cause an insecure certificate error
+ os.remove(os.path.join(custom_profile.profile, "cert9.db"))
+ except Exception:
+ pass
+
+ config = deepcopy(configuration)
+ config["capabilities"]["moz:firefoxOptions"]["args"] = [
+ "--profile",
+ custom_profile.profile,
+ ]
+ # Capability matching not implemented yet for WebDriver BiDi (bug 1713784)
+ config["capabilities"]["acceptInsecureCerts"] = False
+ config["capabilities"]["webSocketUrl"] = True
+
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ bidi_session = driver.session.bidi_session
+ await bidi_session.start()
+
+ contexts = await bidi_session.browsing_context.get_tree(max_depth=0)
+ await navigate_and_assert(
+ bidi_session,
+ contexts[0],
+ url("/common/blank.html", protocol="https"),
+ expected_error=True,
+ )
+
+
+async def test_invalid_content_encoding(bidi_session, new_tab, inline):
+ await navigate_and_assert(
+ bidi_session,
+ new_tab,
+ f"{inline('<div>foo')}&pipe=header(Content-Encoding,gzip)",
+ expected_error=True,
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/invalid.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/invalid.py
new file mode 100644
index 0000000000..b837b86416
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/invalid.py
@@ -0,0 +1,19 @@
+import pytest
+from webdriver.bidi import error
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.parametrize(
+ "viewport",
+ [
+ {"width": 10000001, "height": 100},
+ {"width": 100, "height": 10000001},
+ ],
+ ids=["width exceeded", "height exceeded"],
+)
+async def test_params_viewport_invalid_value(bidi_session, new_tab, viewport):
+ with pytest.raises(error.UnsupportedOperationException):
+ await bidi_session.browsing_context.set_viewport(
+ context=new_tab["context"], viewport=viewport
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/viewport.py b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/viewport.py
new file mode 100644
index 0000000000..ef48ad2926
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/browsing_context/set_viewport/viewport.py
@@ -0,0 +1,18 @@
+import pytest
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.parametrize(
+ "viewport",
+ [
+ {"width": 10000000, "height": 100},
+ {"width": 100, "height": 10000000},
+ {"width": 10000000, "height": 10000000},
+ ],
+ ids=["maximal width", "maximal height", "maximal width and height"],
+)
+async def test_params_viewport_max_value(bidi_session, new_tab, viewport):
+ await bidi_session.browsing_context.set_viewport(
+ context=new_tab["context"], viewport=viewport
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/conftest.py b/testing/web-platform/mozilla/tests/webdriver/bidi/conftest.py
new file mode 100644
index 0000000000..4bd63c8df7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/conftest.py
@@ -0,0 +1,93 @@
+import pytest
+import pytest_asyncio
+from webdriver.bidi.client import BidiSession
+
+
+@pytest.fixture
+def match_capabilities(add_browser_capabilities):
+ def match_capabilities(match_type, capability_key, capability_value):
+ capability = {}
+ capability[capability_key] = capability_value
+ capabilities = add_browser_capabilities(capability)
+ if match_type == "firstMatch":
+ capabilities = [capabilities]
+
+ capabilities_params = {}
+ capabilities_params[match_type] = capabilities
+
+ return capabilities_params
+
+ return match_capabilities
+
+
+@pytest_asyncio.fixture
+async def bidi_client(browser):
+ bidi_session = None
+ current_browser = browser(use_bidi=True)
+
+ async def bidi_client(capabilities={}):
+ nonlocal current_browser
+
+ # Launch the browser again if it's not running.
+ if current_browser.is_running is False:
+ current_browser = browser(use_bidi=True)
+
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+
+ nonlocal bidi_session
+
+ bidi_session = BidiSession.bidi_only(
+ f"ws://{server_host}:{server_port}", requested_capabilities=capabilities
+ )
+ bidi_session.current_browser = current_browser
+
+ await bidi_session.start_transport()
+
+ return bidi_session
+
+ yield bidi_client
+
+ if bidi_session is not None:
+ await bidi_session.end()
+
+
+@pytest_asyncio.fixture
+async def new_session(bidi_client):
+ """Start bidi client and create a new session.
+ At the moment, it throws an error if the session was already started,
+ since multiple sessions are not supported.
+ """
+ bidi_session = None
+
+ async def new_session(capabilities):
+ nonlocal bidi_session
+
+ bidi_session = await bidi_client(capabilities)
+ await bidi_session.start()
+
+ return bidi_session
+
+ yield new_session
+
+ # Check if the browser, the session or websocket connection was not closed already.
+ if (
+ bidi_session is not None
+ and bidi_session.current_browser.is_running is True
+ and bidi_session.session_id is not None
+ and bidi_session.transport.connection.closed is False
+ ):
+ await bidi_session.session.end()
+
+
+@pytest.fixture(name="add_browser_capabilities")
+def fixture_add_browser_capabilities(configuration):
+ def add_browser_capabilities(capabilities):
+ # Make sure there aren't keys in common.
+ assert not set(configuration["capabilities"]).intersection(set(capabilities))
+ result = dict(configuration["capabilities"])
+ result.update(capabilities)
+
+ return result
+
+ return add_browser_capabilities
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py
new file mode 100644
index 0000000000..69b1f2fb7a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/errors/errors.py
@@ -0,0 +1,8 @@
+import pytest
+from webdriver.bidi.error import UnknownCommandException
+
+
+@pytest.mark.asyncio
+async def test_internal_method(bidi_session, send_blocking_command):
+ with pytest.raises(UnknownCommandException):
+ await send_blocking_command("log._applySessionData", {})
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py
new file mode 100644
index 0000000000..c7924ca851
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/interface/interface.py
@@ -0,0 +1,26 @@
+import pytest
+from webdriver.bidi.client import BidiSession
+from webdriver.bidi.modules.script import ContextTarget
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_navigator_webdriver_enabled(inline, browser):
+ # Request a new browser with only WebDriver BiDi and not Marionette/CDP enabled.
+ current_browser = browser(use_bidi=True, extra_prefs={"remote.active-protocols": 1})
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+
+ async with BidiSession.bidi_only(
+ f"ws://{server_host}:{server_port}", requested_capabilities={"alwaysMatch": {}}
+ ) as bidi_session:
+ contexts = await bidi_session.browsing_context.get_tree(max_depth=0)
+ assert len(contexts) > 0
+
+ result = await bidi_session.script.evaluate(
+ expression="navigator.webdriver",
+ target=ContextTarget(contexts[0]["context"]),
+ await_promise=False,
+ )
+
+ assert result == {"type": "boolean", "value": True}
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/script/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/script/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/script/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py b/testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py
new file mode 100644
index 0000000000..2af2d5d24f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/script/exception_details.py
@@ -0,0 +1,61 @@
+import pytest
+from webdriver.bidi.modules.script import ContextTarget, ScriptEvaluateResultException
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("await_promise", [True, False])
+@pytest.mark.parametrize(
+ "expression",
+ [
+ "null",
+ "{ toString: 'not a function' }",
+ "{ toString: () => {{ throw 'toString not allowed'; }} }",
+ "{ toString: () => true }",
+ ],
+)
+@pytest.mark.asyncio
+async def test_call_function_without_to_string_interface(
+ bidi_session, top_context, await_promise, expression
+):
+ function_declaration = "()=>{throw { toString: 'not a function' } }"
+ if await_promise:
+ function_declaration = "async" + function_declaration
+
+ with pytest.raises(ScriptEvaluateResultException) as exception:
+ await bidi_session.script.call_function(
+ function_declaration=function_declaration,
+ await_promise=await_promise,
+ target=ContextTarget(top_context["context"]),
+ )
+
+ assert isinstance(exception.value.text, str)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("await_promise", [True, False])
+@pytest.mark.parametrize(
+ "expression",
+ [
+ "null",
+ "{ toString: 'not a function' }",
+ "{ toString: () => {{ throw 'toString not allowed'; }} }",
+ "{ toString: () => true }",
+ ],
+)
+@pytest.mark.asyncio
+async def test_evaluate_without_to_string_interface(
+ bidi_session, top_context, await_promise, expression
+):
+ if await_promise:
+ expression = f"Promise.reject({expression})"
+ else:
+ expression = f"throw {expression}"
+
+ with pytest.raises(ScriptEvaluateResultException) as exception:
+ await bidi_session.script.evaluate(
+ expression=expression,
+ await_promise=await_promise,
+ target=ContextTarget(top_context["context"]),
+ )
+
+ assert isinstance(exception.value.text, str)
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/end.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/end.py
new file mode 100644
index 0000000000..f1f9c84263
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/end.py
@@ -0,0 +1,53 @@
+import pytest
+from webdriver.bidi.error import InvalidSessionIDError
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_session_end(new_session, add_browser_capabilities, bidi_client):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+
+ await bidi_session.session.end()
+
+ # Connect the client again.
+ not_active_bidi_session = await bidi_client()
+ response = await not_active_bidi_session.session.status()
+
+ # Make sure that session can be created.
+ assert response["ready"] is True
+
+
+async def test_start_session_again(new_session, add_browser_capabilities):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+ first_session_id = bidi_session.session_id
+
+ await bidi_session.session.end()
+
+ # Try to create a session again
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+
+ assert isinstance(bidi_session.session_id, str)
+ assert first_session_id != bidi_session.session_id
+
+
+async def test_send_the_command_after_session_end(
+ new_session, add_browser_capabilities, bidi_client
+):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+
+ await bidi_session.session.end()
+
+ # Connect the client again.
+ not_active_bidi_session = await bidi_client()
+
+ # Make sure that command will fail, since the session was closed.
+ with pytest.raises(InvalidSessionIDError):
+ await not_active_bidi_session.browsing_context.create(type_hint="tab")
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/invalid.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/invalid.py
new file mode 100644
index 0000000000..1ffe57c615
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/end/invalid.py
@@ -0,0 +1,15 @@
+import pytest
+from webdriver.bidi.error import InvalidSessionIDError
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_without_session(bidi_client):
+ # Connect the client.
+ bidi_session = await bidi_client()
+ response = await bidi_session.session.status()
+
+ assert response["ready"] is True
+
+ with pytest.raises(InvalidSessionIDError):
+ await bidi_session.session.end()
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/always_match.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/always_match.py
new file mode 100644
index 0000000000..50f1314a47
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/always_match.py
@@ -0,0 +1,18 @@
+# META: timeout=long
+
+import pytest
+
+from bidi.session.new.support.test_data import flat_valid_data
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.parametrize("key,value", flat_valid_data)
+async def test_valid(new_session, add_browser_capabilities, key, value):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({key: value})}
+ )
+
+ assert bidi_session.capabilities is not None
+ if value is not None:
+ assert key in bidi_session.capabilities
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/capabilities.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/capabilities.py
new file mode 100644
index 0000000000..f709198eca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/capabilities.py
@@ -0,0 +1,51 @@
+import pytest
+from tests.support import platform_name
+from webdriver.bidi.modules.script import ContextTarget
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.skipif(
+ platform_name is None, reason="Unsupported platform {}".format(platform_name)
+)
+@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
+async def test_platform_name(new_session, match_capabilities, match_type):
+ capabilities = match_capabilities(match_type, "platformName", platform_name)
+
+ bidi_session = await new_session(capabilities=capabilities)
+
+ assert bidi_session.capabilities["platformName"] == platform_name
+
+
+@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
+async def test_proxy(
+ new_session, match_capabilities, server_config, inline, match_type
+):
+ domain = server_config["domains"][""][""]
+ port = server_config["ports"]["http"][0]
+ proxy_url = f"{domain}:{port}"
+ proxy_capability = {"proxyType": "manual", "httpProxy": proxy_url}
+ capabilities = match_capabilities(match_type, "proxy", proxy_capability)
+
+ bidi_session = await new_session(capabilities=capabilities)
+
+ assert bidi_session.capabilities["proxy"] == proxy_capability
+
+ page_content = "proxy"
+ page_url = inline(f"<div>{page_content}</div>")
+ test_url = page_url.replace(proxy_url, "example.com")
+
+ contexts = await bidi_session.browsing_context.get_tree()
+
+ await bidi_session.browsing_context.navigate(
+ context=contexts[0]["context"], url=test_url, wait="complete"
+ )
+
+ # Check that content is expected
+ response = await bidi_session.script.evaluate(
+ expression="""document.querySelector('div').textContent""",
+ target=ContextTarget(contexts[0]["context"]),
+ await_promise=False,
+ )
+
+ assert response == {"type": "string", "value": page_content}
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/first_match.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/first_match.py
new file mode 100644
index 0000000000..2e7fd00607
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/first_match.py
@@ -0,0 +1,18 @@
+# META: timeout=long
+
+import pytest
+
+from bidi.session.new.support.test_data import flat_valid_data
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.parametrize("key,value", flat_valid_data)
+async def test_valid(new_session, add_browser_capabilities, key, value):
+ bidi_session = await new_session(
+ capabilities={"firstMatch": [add_browser_capabilities({key: value})]}
+ )
+
+ assert bidi_session.capabilities is not None
+ if value is not None:
+ assert key in bidi_session.capabilities
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/invalid.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/invalid.py
new file mode 100644
index 0000000000..8736933631
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/invalid.py
@@ -0,0 +1,66 @@
+import pytest
+from webdriver.bidi.error import InvalidArgumentException
+
+from bidi.session.new.support.test_data import flat_invalid_data, invalid_extensions
+
+pytestmark = pytest.mark.asyncio
+
+
+@pytest.mark.parametrize("value", [None, True, 1, "{}", []])
+async def test_params_capabilities_invalid_type(new_session, value):
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities=value)
+
+
+@pytest.mark.parametrize("value", [True, 1, "{}", []])
+async def test_params_always_match_invalid_type(new_session, value):
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities={"alwaysMatch": value})
+
+
+@pytest.mark.parametrize("value", [True, 1, "{}", {}, []])
+async def test_params_first_match_invalid_type(new_session, value):
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities={"firstMatch": value})
+
+
+@pytest.mark.parametrize("value", [True, 1, "{}", None, []])
+async def test_params_first_match_item_invalid_type(new_session, value):
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities={"firstMatch": [value]})
+
+
+@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
+@pytest.mark.parametrize("key,value", flat_invalid_data)
+async def test_invalid_value(new_session, match_capabilities, match_type, key, value):
+ capabilities = match_capabilities(match_type, key, value)
+
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities=capabilities)
+
+
+@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
+@pytest.mark.parametrize("key", invalid_extensions)
+async def test_invalid_extension(new_session, match_capabilities, match_type, key):
+ capabilities = match_capabilities(match_type, key, {})
+
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities=capabilities)
+
+
+@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
+async def test_invalid_moz_extension(new_session, match_capabilities, match_type):
+ capabilities = match_capabilities(match_type, "moz:someRandomString", {})
+
+ with pytest.raises(InvalidArgumentException):
+ await new_session(capabilities=capabilities)
+
+
+async def test_params_with_shadowed_value(new_session):
+ with pytest.raises(InvalidArgumentException):
+ await new_session(
+ capabilities={
+ "firstMatch": [{"acceptInsecureCerts": True}],
+ "alwaysMatch": {"acceptInsecureCerts": True},
+ }
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/response.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/response.py
new file mode 100644
index 0000000000..c635b7c93f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/response.py
@@ -0,0 +1,84 @@
+# META: timeout=long
+
+import uuid
+
+import pytest
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_session_id(new_session, add_browser_capabilities):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+ assert isinstance(bidi_session.session_id, str)
+ uuid.UUID(hex=bidi_session.session_id)
+
+
+async def test_capability_type(new_session, add_browser_capabilities):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+
+ default_capability_types = [
+ ("acceptInsecureCerts", bool),
+ ("browserName", str),
+ ("browserVersion", str),
+ ("platformName", str),
+ ("proxy", dict),
+ ("setWindowRect", bool),
+ ]
+
+ assert isinstance(bidi_session.capabilities, dict)
+
+ for capability, type in default_capability_types:
+ assert isinstance(bidi_session.capabilities[capability], type)
+
+
+async def test_capability_default_value(new_session, add_browser_capabilities):
+ bidi_session = await new_session(
+ capabilities={"alwaysMatch": add_browser_capabilities({})}
+ )
+ assert isinstance(bidi_session.capabilities, dict)
+
+ default_capability_values = [
+ ("acceptInsecureCerts", False),
+ ("proxy", {}),
+ ]
+
+ for capability, value in default_capability_values:
+ assert bidi_session.capabilities[capability] == value
+
+
+async def test_ignore_non_spec_fields_in_capabilities(
+ new_session, add_browser_capabilities
+):
+ bidi_session = await new_session(
+ capabilities={
+ "alwaysMatch": add_browser_capabilities({}),
+ "nonSpecCapabilities": {"acceptInsecureCerts": True},
+ }
+ )
+
+ assert bidi_session.capabilities["acceptInsecureCerts"] is False
+
+
+@pytest.mark.parametrize("match_type", ["alwaysMatch", "firstMatch"])
+@pytest.mark.parametrize(
+ "key, value",
+ [
+ ("pageLoadStrategy", "none"),
+ ("strictFileInteractability", True),
+ ("timeouts", {"script": 500}),
+ ("unhandledPromptBehavior", "accept"),
+ ],
+)
+async def test_with_webdriver_classic_capabilities(
+ new_session, match_capabilities, match_type, key, value
+):
+ capabilities = match_capabilities(match_type, key, value)
+
+ bidi_session = await new_session(capabilities=capabilities)
+
+ assert isinstance(bidi_session.capabilities, dict)
+ assert key not in bidi_session.capabilities
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/test_data.py b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/test_data.py
new file mode 100644
index 0000000000..7a2b41a06e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/session/new/support/test_data.py
@@ -0,0 +1,143 @@
+def product(a, b):
+ return [(a, item) for item in b]
+
+
+def flatten(l):
+ return [item for x in l for item in x]
+
+
+# Note that we can only test things here all implementations must support
+valid_data = [
+ (
+ "acceptInsecureCerts",
+ [
+ False,
+ None,
+ ],
+ ),
+ (
+ "browserName",
+ [
+ None,
+ ],
+ ),
+ (
+ "browserVersion",
+ [
+ None,
+ ],
+ ),
+ (
+ "platformName",
+ [
+ None,
+ ],
+ ),
+ (
+ "proxy",
+ [
+ None,
+ ],
+ ),
+ (
+ "test:extension",
+ [
+ None,
+ False,
+ "abc",
+ 123,
+ [],
+ {"key": "value"},
+ ],
+ ),
+]
+
+flat_valid_data = flatten(product(*item) for item in valid_data)
+
+invalid_data = [
+ (
+ "acceptInsecureCerts",
+ [
+ 1,
+ [],
+ {},
+ "false",
+ ],
+ ),
+ (
+ "browserName",
+ [
+ 1,
+ [],
+ {},
+ False,
+ ],
+ ),
+ (
+ "browserVersion",
+ [
+ 1,
+ [],
+ {},
+ False,
+ ],
+ ),
+ (
+ "platformName",
+ [
+ 1,
+ [],
+ {},
+ False,
+ ],
+ ),
+ (
+ "proxy",
+ [
+ 1,
+ [],
+ "{}",
+ {"proxyType": "SYSTEM"},
+ {"proxyType": "systemSomething"},
+ {"proxy type": "pac"},
+ {"proxy-Type": "system"},
+ {"proxy_type": "system"},
+ {"proxytype": "system"},
+ {"PROXYTYPE": "system"},
+ {"proxyType": None},
+ {"proxyType": 1},
+ {"proxyType": []},
+ {"proxyType": {"value": "system"}},
+ {" proxyType": "system"},
+ {"proxyType ": "system"},
+ {"proxyType ": " system"},
+ {"proxyType": "system "},
+ ],
+ ),
+]
+
+flat_invalid_data = flatten(product(*item) for item in invalid_data)
+
+invalid_extensions = [
+ "automaticInspection",
+ "automaticProfiling",
+ "browser",
+ "chromeOptions",
+ "ensureCleanSession",
+ "firefox",
+ "firefox_binary",
+ "firefoxOptions",
+ "initialBrowserUrl",
+ "javascriptEnabled",
+ "logFile",
+ "logLevel",
+ "nativeEvents",
+ "platform",
+ "platformVersion",
+ "profile",
+ "requireWindowFocus",
+ "safari.options",
+ "seleniumProtocol",
+ "trustAllSSLCertificates",
+ "version",
+]
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/storage/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/partition.py b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/partition.py
new file mode 100644
index 0000000000..b037c30038
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/get_cookies/partition.py
@@ -0,0 +1,131 @@
+import pytest
+from tests.bidi import recursive_compare
+from tests.support.helpers import get_origin_from_url
+from webdriver.bidi.modules.storage import BrowsingContextPartitionDescriptor
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_partition_context(
+ bidi_session,
+ new_tab,
+ test_page,
+ domain_value,
+ add_cookie,
+ top_context,
+ test_page_cross_origin,
+):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"], url=test_page, wait="complete"
+ )
+ source_origin_1 = get_origin_from_url(test_page)
+
+ await bidi_session.browsing_context.navigate(
+ context=top_context["context"], url=test_page_cross_origin, wait="complete"
+ )
+ source_origin_2 = get_origin_from_url(test_page_cross_origin)
+
+ cookie_name = "foo"
+ cookie_value = "bar"
+ await add_cookie(new_tab["context"], cookie_name, cookie_value)
+
+ # Check that added cookies are present on the right context.
+ cookies = await bidi_session.storage.get_cookies(
+ partition=BrowsingContextPartitionDescriptor(new_tab["context"])
+ )
+
+ recursive_compare(
+ {
+ "cookies": [
+ {
+ "domain": domain_value(),
+ "httpOnly": False,
+ "name": cookie_name,
+ "path": "/webdriver/tests/support",
+ "sameSite": "none",
+ "secure": False,
+ "size": 6,
+ "value": {"type": "string", "value": cookie_value},
+ }
+ ],
+ "partitionKey": {"sourceOrigin": source_origin_1},
+ },
+ cookies,
+ )
+
+ # Check that added cookies are not present on the other context.
+ cookies = await bidi_session.storage.get_cookies(
+ partition=BrowsingContextPartitionDescriptor(top_context["context"])
+ )
+
+ recursive_compare(
+ {"cookies": [], "partitionKey": {"sourceOrigin": source_origin_2}}, cookies
+ )
+
+
+@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
+async def test_partition_context_iframe(
+ bidi_session, new_tab, inline, domain_value, domain, add_cookie
+):
+ iframe_url = inline("<div id='in-iframe'>foo</div>", domain=domain)
+ source_origin_for_iframe = get_origin_from_url(iframe_url)
+ page_url = inline(f"<iframe src='{iframe_url}'></iframe>")
+ source_origin_for_page = get_origin_from_url(page_url)
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"], url=page_url, wait="complete"
+ )
+
+ contexts = await bidi_session.browsing_context.get_tree(root=new_tab["context"])
+ iframe_context = contexts[0]["children"][0]
+
+ cookie_name = "foo"
+ cookie_value = "bar"
+ await add_cookie(iframe_context["context"], cookie_name, cookie_value)
+
+ # Check that added cookies are present on the right context
+ cookies = await bidi_session.storage.get_cookies(
+ partition=BrowsingContextPartitionDescriptor(iframe_context["context"])
+ )
+
+ expected_cookies = [
+ {
+ "domain": domain_value(domain=domain),
+ "httpOnly": False,
+ "name": cookie_name,
+ "path": "/webdriver/tests/support",
+ "sameSite": "none",
+ "secure": False,
+ "size": 6,
+ "value": {"type": "string", "value": cookie_value},
+ }
+ ]
+ recursive_compare(
+ {
+ "cookies": expected_cookies,
+ "partitionKey": {"sourceOrigin": source_origin_for_iframe},
+ },
+ cookies,
+ )
+
+ cookies = await bidi_session.storage.get_cookies(
+ partition=BrowsingContextPartitionDescriptor(new_tab["context"])
+ )
+ # When the iframe is on the different domain we can verify that top context has no iframe cookie.
+ if domain == "alt":
+ recursive_compare(
+ {
+ "cookies": [],
+ "partitionKey": {"sourceOrigin": source_origin_for_page},
+ },
+ cookies,
+ )
+ else:
+ # When the iframe is on the same domain, since the browsing context partition is defined by user context and origin,
+ # which will be the same for the page, we get the same cookies as for the iframe
+ recursive_compare(
+ {
+ "cookies": expected_cookies,
+ "partitionKey": {"sourceOrigin": source_origin_for_page},
+ },
+ cookies,
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/__init__.py b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/partition.py b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/partition.py
new file mode 100644
index 0000000000..f8e2823dbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/storage/set_cookie/partition.py
@@ -0,0 +1,150 @@
+import pytest
+from tests.bidi import recursive_compare
+from tests.support.helpers import get_origin_from_url
+from webdriver.bidi.modules.network import NetworkStringValue
+from webdriver.bidi.modules.storage import (
+ BrowsingContextPartitionDescriptor,
+ PartialCookie,
+)
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_partition_context(
+ bidi_session,
+ set_cookie,
+ top_context,
+ new_tab,
+ test_page,
+ test_page_cross_origin,
+ domain_value,
+):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"], url=test_page, wait="complete"
+ )
+ source_origin_1 = get_origin_from_url(test_page)
+ await bidi_session.browsing_context.navigate(
+ context=top_context["context"], url=test_page_cross_origin, wait="complete"
+ )
+ source_origin_2 = get_origin_from_url(test_page_cross_origin)
+
+ cookie_name = "foo"
+ cookie_value = "bar"
+ cookie_domain = domain_value()
+ new_tab_partition = BrowsingContextPartitionDescriptor(new_tab["context"])
+
+ set_cookie_result = await set_cookie(
+ cookie=PartialCookie(
+ domain=cookie_domain,
+ name=cookie_name,
+ value=NetworkStringValue(cookie_value),
+ ),
+ partition=new_tab_partition,
+ )
+
+ assert set_cookie_result == {"partitionKey": {"sourceOrigin": source_origin_1}}
+
+ # Check that added cookies are present on the right context.
+ cookies = await bidi_session.storage.get_cookies(partition=new_tab_partition)
+
+ recursive_compare(
+ {
+ "cookies": [
+ {
+ "domain": cookie_domain,
+ "httpOnly": False,
+ "name": cookie_name,
+ "path": "/",
+ "sameSite": "none",
+ "secure": False,
+ "size": 6,
+ "value": {"type": "string", "value": cookie_value},
+ }
+ ],
+ "partitionKey": {"sourceOrigin": source_origin_1},
+ },
+ cookies,
+ )
+
+ # Check that added cookies are not present on the other context.
+ cookies = await bidi_session.storage.get_cookies(
+ partition=BrowsingContextPartitionDescriptor(top_context["context"])
+ )
+
+ recursive_compare(
+ {"cookies": [], "partitionKey": {"sourceOrigin": source_origin_2}}, cookies
+ )
+
+
+@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
+async def test_partition_context_iframe(
+ bidi_session, new_tab, inline, domain_value, domain, set_cookie
+):
+ iframe_url = inline("<div id='in-iframe'>foo</div>", domain=domain)
+ source_origin_for_iframe = get_origin_from_url(iframe_url)
+ page_url = inline(f"<iframe src='{iframe_url}'></iframe>")
+ source_origin_for_page = get_origin_from_url(page_url)
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"], url=page_url, wait="complete"
+ )
+
+ contexts = await bidi_session.browsing_context.get_tree(root=new_tab["context"])
+ iframe_context = contexts[0]["children"][0]
+ iframe_partition = BrowsingContextPartitionDescriptor(iframe_context["context"])
+
+ cookie_name = "foo"
+ cookie_value = "bar"
+ await set_cookie(
+ cookie=PartialCookie(
+ domain=domain_value(domain),
+ name=cookie_name,
+ value=NetworkStringValue(cookie_value),
+ ),
+ partition=iframe_partition,
+ )
+
+ # Check that added cookies are present on the right context
+ cookies = await bidi_session.storage.get_cookies(partition=iframe_partition)
+
+ expected_cookies = [
+ {
+ "domain": domain_value(domain=domain),
+ "httpOnly": False,
+ "name": cookie_name,
+ "path": "/",
+ "sameSite": "none",
+ "secure": False,
+ "size": 6,
+ "value": {"type": "string", "value": cookie_value},
+ }
+ ]
+ recursive_compare(
+ {
+ "cookies": expected_cookies,
+ "partitionKey": {"sourceOrigin": source_origin_for_iframe},
+ },
+ cookies,
+ )
+
+ cookies = await bidi_session.storage.get_cookies(
+ partition=BrowsingContextPartitionDescriptor(new_tab["context"])
+ )
+ # When the iframe is on the different domain we can verify that top context has no iframe cookie.
+ if domain == "alt":
+ recursive_compare(
+ {
+ "cookies": [],
+ "partitionKey": {"sourceOrigin": source_origin_for_page},
+ },
+ cookies,
+ )
+ else:
+ # When the iframe is on the same domain, since the browsing context partition is defined by user context and origin,
+ # which will be the same for the page, we get the same cookies as for the iframe
+ recursive_compare(
+ {
+ "cookies": expected_cookies,
+ "partitionKey": {"sourceOrigin": source_origin_for_page},
+ },
+ cookies,
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py b/testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py
new file mode 100644
index 0000000000..69f48aa9d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/bidi/websocket_upgrade.py
@@ -0,0 +1,158 @@
+# META: timeout=long
+
+import pytest
+from support.network import get_host, websocket_request
+
+
+@pytest.mark.parametrize(
+ "hostname, port_type, status",
+ [
+ # Valid hosts
+ ("localhost", "server_port", 101),
+ ("localhost", "default_port", 101),
+ ("127.0.0.1", "server_port", 101),
+ ("127.0.0.1", "default_port", 101),
+ ("[::1]", "server_port", 101),
+ ("[::1]", "default_port", 101),
+ ("192.168.8.1", "server_port", 101),
+ ("192.168.8.1", "default_port", 101),
+ ("[fdf8:f535:82e4::53]", "server_port", 101),
+ ("[fdf8:f535:82e4::53]", "default_port", 101),
+ # Invalid hosts
+ ("mozilla.org", "server_port", 400),
+ ("mozilla.org", "wrong_port", 400),
+ ("mozilla.org", "default_port", 400),
+ ("localhost", "wrong_port", 400),
+ ("127.0.0.1", "wrong_port", 400),
+ ("[::1]", "wrong_port", 400),
+ ("192.168.8.1", "wrong_port", 400),
+ ("[fdf8:f535:82e4::53]", "wrong_port", 400),
+ ],
+ ids=[
+ # Valid hosts
+ "localhost with same port as RemoteAgent",
+ "localhost with default port",
+ "127.0.0.1 (loopback) with same port as RemoteAgent",
+ "127.0.0.1 (loopback) with default port",
+ "[::1] (ipv6 loopback) with same port as RemoteAgent",
+ "[::1] (ipv6 loopback) with default port",
+ "ipv4 address with same port as RemoteAgent",
+ "ipv4 address with default port",
+ "ipv6 address with same port as RemoteAgent",
+ "ipv6 address with default port",
+ # Invalid hosts
+ "random hostname with the same port as RemoteAgent",
+ "random hostname with a different port than RemoteAgent",
+ "random hostname with default port",
+ "localhost with a different port than RemoteAgent",
+ "127.0.0.1 (loopback) with a different port than RemoteAgent",
+ "[::1] (ipv6 loopback) with a different port than RemoteAgent",
+ "ipv4 address with a different port than RemoteAgent",
+ "ipv6 address with a different port than RemoteAgent",
+ ],
+)
+def test_host_header(browser, hostname, port_type, status):
+ # Request a default browser
+ current_browser = browser(use_bidi=True)
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+ test_host = get_host(port_type, hostname, server_port)
+
+ response = websocket_request(server_host, server_port, host=test_host)
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "hostname, port_type, status",
+ [
+ # Allowed hosts
+ ("testhost", "server_port", 101),
+ ("testhost", "default_port", 101),
+ ("testhost", "wrong_port", 400),
+ # IP addresses
+ ("192.168.8.1", "server_port", 101),
+ ("192.168.8.1", "default_port", 101),
+ ("[fdf8:f535:82e4::53]", "server_port", 101),
+ ("[fdf8:f535:82e4::53]", "default_port", 101),
+ ("127.0.0.1", "server_port", 101),
+ ("127.0.0.1", "default_port", 101),
+ ("[::1]", "server_port", 101),
+ ("[::1]", "default_port", 101),
+ # Localhost
+ ("localhost", "server_port", 400),
+ ("localhost", "default_port", 400),
+ ],
+ ids=[
+ # Allowed hosts
+ "allowed host with same port as RemoteAgent",
+ "allowed host with default port",
+ "allowed host with wrong port",
+ # IP addresses
+ "ipv4 address with same port as RemoteAgent",
+ "ipv4 address with default port",
+ "ipv6 address with same port as RemoteAgent",
+ "ipv6 address with default port",
+ "127.0.0.1 (loopback) with same port as RemoteAgent",
+ "127.0.0.1 (loopback) with default port",
+ "[::1] (ipv6 loopback) with same port as RemoteAgent",
+ "[::1] (ipv6 loopback) with default port",
+ # Localhost
+ "localhost with same port as RemoteAgent",
+ "localhost with default port",
+ ],
+)
+def test_allowed_hosts(browser, hostname, port_type, status):
+ # Request a browser with custom allowed hosts.
+ current_browser = browser(
+ use_bidi=True,
+ extra_args=["--remote-allow-hosts", "testhost"],
+ )
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+ test_host = get_host(port_type, hostname, server_port)
+
+ response = websocket_request(server_host, server_port, host=test_host)
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "origin, status",
+ [
+ (None, 101),
+ ("", 400),
+ ("sometext", 400),
+ ("http://localhost:1234", 400),
+ ],
+)
+def test_origin_header(browser, origin, status):
+ # Request a default browser.
+ current_browser = browser(use_bidi=True)
+ server_host = current_browser.remote_agent_host
+ server_port = current_browser.remote_agent_port
+ response = websocket_request(server_host, server_port, origin=origin)
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "origin, status",
+ [
+ (None, 101),
+ ("", 400),
+ ("sometext", 400),
+ ("http://localhost:1234", 101),
+ ("https://localhost:1234", 400),
+ ],
+)
+def test_allowed_origins(browser, origin, status):
+ # Request a browser with custom allowed origins.
+ current_browser = browser(
+ use_bidi=True,
+ extra_args=["--remote-allow-origins", "http://localhost:1234"],
+ )
+ server_port = current_browser.remote_agent_port
+
+ # Both `localhost` and `127.0.0.1` have to accept connections.
+ for target_host in ["127.0.0.1", "localhost"]:
+ print(f"Connecting to the WebSocket via host {target_host}")
+ response = websocket_request(target_host, server_port, origin=origin)
+ assert response.status == status
diff --git a/testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py b/testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/cdp/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py b/testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py
new file mode 100644
index 0000000000..ef9a301d24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/cdp/debugger_address.py
@@ -0,0 +1,45 @@
+# META: timeout=long
+
+import json
+
+import pytest
+from support.context import using_context
+from tests.support.http_request import HTTPRequest
+
+
+def test_debugger_address_not_set(session):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is None
+
+
+@pytest.mark.capabilities({"moz:debuggerAddress": False})
+def test_debugger_address_false(session):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is None
+
+
+@pytest.mark.capabilities({"moz:debuggerAddress": True})
+@pytest.mark.parametrize("fission_enabled", [True, False], ids=["enabled", "disabled"])
+def test_debugger_address_true_with_fission(session, fission_enabled):
+ debugger_address = session.capabilities.get("moz:debuggerAddress")
+ assert debugger_address is not None
+
+ host, port = debugger_address.split(":")
+ assert host == "127.0.0.1"
+ assert port.isnumeric()
+
+ # Fetch the browser version via the debugger address, `localhost` has
+ # to work as well.
+ for target_host in [host, "localhost"]:
+ print(f"Connecting to WebSocket via host {target_host}")
+ http = HTTPRequest(target_host, int(port))
+ with http.get("/json/version") as response:
+ data = json.loads(response.read())
+ assert session.capabilities["browserVersion"] in data["Browser"]
+
+ # Ensure Fission is not disabled (bug 1813981)
+ with using_context(session, "chrome"):
+ assert (
+ session.execute_script("""return Services.appinfo.fissionAutostart""")
+ is fission_enabled
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py b/testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py
new file mode 100644
index 0000000000..aa294deb24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/cdp/port_file.py
@@ -0,0 +1,30 @@
+import os
+
+from support.network import websocket_request
+
+
+def test_devtools_active_port_file(browser):
+ current_browser = browser(use_cdp=True)
+
+ assert current_browser.remote_agent_port != 0
+ assert current_browser.debugger_address.startswith("/devtools/browser/")
+
+ port_file = os.path.join(current_browser.profile.profile, "DevToolsActivePort")
+ assert os.path.exists(port_file)
+
+ current_browser.quit(clean_profile=False)
+ assert not os.path.exists(port_file)
+
+
+def test_connect(browser):
+ current_browser = browser(use_cdp=True)
+
+ # Both `localhost` and `127.0.0.1` have to accept connections.
+ for target_host in ["127.0.0.1", "localhost"]:
+ print(f"Connecting to the WebSocket via host {target_host}")
+ response = websocket_request(
+ target_host,
+ current_browser.remote_agent_port,
+ path=current_browser.debugger_address,
+ )
+ assert response.status == 101
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/scroll_into_view.py b/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/scroll_into_view.py
new file mode 100644
index 0000000000..080195d345
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/scroll_into_view.py
@@ -0,0 +1,50 @@
+from tests.support.asserts import assert_success
+from tests.support.helpers import is_element_in_viewport
+
+
+def element_send_keys(session, element, text):
+ return session.transport.send(
+ "POST",
+ "/session/{session_id}/element/{element_id}/value".format(
+ session_id=session.session_id, element_id=element.id
+ ),
+ {"text": text},
+ )
+
+
+def test_option_select_container_outside_of_scrollable_viewport(session, inline):
+ session.url = inline(
+ """
+ <select style="margin-top: 102vh;">
+ <option value="foo">foo</option>
+ <option value="bar" id="bar">bar</option>
+ </select>
+ """
+ )
+ element = session.find.css("option#bar", all=False)
+ select = session.find.css("select", all=False)
+
+ response = element_send_keys(session, element, "bar")
+ assert_success(response)
+
+ assert is_element_in_viewport(session, select)
+ assert is_element_in_viewport(session, element)
+
+
+def test_option_stays_outside_of_scrollable_viewport(session, inline):
+ session.url = inline(
+ """
+ <select multiple style="height: 105vh; margin-top: 100vh;">
+ <option value="foo" id="foo" style="height: 100vh;">foo</option>
+ <option value="bar" id="bar" style="background-color: yellow;">bar</option>
+ </select>
+ """
+ )
+ select = session.find.css("select", all=False)
+ option_bar = session.find.css("option#bar", all=False)
+
+ response = element_send_keys(session, option_bar, "bar")
+ assert_success(response)
+
+ assert is_element_in_viewport(session, select)
+ assert is_element_in_viewport(session, option_bar)
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/send_keys.py b/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/send_keys.py
new file mode 100644
index 0000000000..acdcaae74d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/element_send_keys/send_keys.py
@@ -0,0 +1,44 @@
+import pytest
+from tests.support.asserts import assert_success
+from tests.support.keys import Keys
+
+
+def element_send_keys(session, element, text):
+ return session.transport.send(
+ "POST",
+ "/session/{session_id}/element/{element_id}/value".format(
+ session_id=session.session_id, element_id=element.id
+ ),
+ {"text": text},
+ )
+
+
+def test_modifier_key_toggles(session, inline, modifier_key):
+ session.url = inline("<input type=text value=foo>")
+ element = session.find.css("input", all=False)
+
+ response = element_send_keys(
+ session, element, f"{modifier_key}a{modifier_key}{Keys.DELETE}cheese"
+ )
+ assert_success(response)
+
+ assert element.property("value") == "cheese"
+
+
+@pytest.mark.parametrize("dispatch_once_per_surrogate_pair", [False, True])
+def test_dispatch_once_per_surrogate_pair(
+ session, use_pref, inline, dispatch_once_per_surrogate_pair
+):
+ use_pref(
+ "dom.event.keypress.dispatch_once_per_surrogate_pair",
+ dispatch_once_per_surrogate_pair,
+ )
+
+ session.url = inline("<input>")
+ element = session.find.css("input", all=False)
+
+ text = "🦥🍄"
+ response = element_send_keys(session, element, text)
+ assert_success(response)
+
+ assert element.property("value") == text
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/execute_async.py b/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/execute_async.py
new file mode 100644
index 0000000000..fe42e44ce7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/execute_async_script/execute_async.py
@@ -0,0 +1,71 @@
+import pytest
+from tests.support.asserts import assert_success
+from tests.support.sync import Poll
+from webdriver.error import NoSuchAlertException
+
+
+def execute_async_script(session, script, args=None):
+ if args is None:
+ args = []
+ body = {"script": script, "args": args}
+
+ return session.transport.send(
+ "POST", "/session/{session_id}/execute/async".format(**vars(session)), body
+ )
+
+
+@pytest.mark.parametrize("dialog_type", ["alert", "confirm", "prompt"])
+def test_no_abort_by_user_prompt_in_other_tab(session, inline, dialog_type):
+ original_handle = session.window_handle
+ original_handles = session.handles
+
+ session.url = inline(
+ """
+ <a onclick="window.open();">open window</a>
+ <script>
+ window.addEventListener("message", function (event) {{
+ {}("foo");
+ }});
+ </script>
+ """.format(
+ dialog_type
+ )
+ )
+
+ session.find.css("a", all=False).click()
+ wait = Poll(session, timeout=5, message="No new window has been opened")
+ new_handles = wait.until(lambda s: set(s.handles) - set(original_handles))
+ assert len(new_handles) == 1
+
+ session.window_handle = new_handles.pop()
+
+ response = execute_async_script(
+ session,
+ """
+ const resolve = arguments[0];
+
+ // Trigger opening a user prompt in the other window.
+ window.opener.postMessage("foo", "*");
+
+ // Delay resolving the Promise to ensure a user prompt has been opened.
+ setTimeout(() => resolve(42), 500);
+ """,
+ )
+
+ assert_success(response, 42)
+
+ session.window.close()
+
+ session.window_handle = original_handle
+
+ # Opening the alert in a different window is async here and can cause
+ # delays in slow builds like CCOV or TSAN.
+ wait = Poll(
+ session,
+ timeout=15,
+ ignored_exceptions=NoSuchAlertException,
+ message="No user prompt with text 'foo' detected",
+ )
+ wait.until(lambda s: s.alert.text == "foo")
+
+ session.alert.accept()
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/chrome.py b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/chrome.py
new file mode 100644
index 0000000000..af24be4b9e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handle/chrome.py
@@ -0,0 +1,25 @@
+from support.context import using_context
+from tests.support.asserts import assert_success
+
+
+def get_window_handle(session):
+ return session.transport.send(
+ "GET", "session/{session_id}/window".format(**vars(session))
+ )
+
+
+def test_basic(session):
+ with using_context(session, "chrome"):
+ response = get_window_handle(session)
+ assert_success(response, session.window_handle)
+
+
+def test_different_handle_than_content_scope(session):
+ response = get_window_handle(session)
+ content_handle = assert_success(response)
+
+ with using_context(session, "chrome"):
+ response = get_window_handle(session)
+ chrome_handle = assert_success(response)
+
+ assert chrome_handle != content_handle
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/chrome.py b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/chrome.py
new file mode 100644
index 0000000000..091ac01e6c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/get_window_handles/chrome.py
@@ -0,0 +1,43 @@
+from support.context import using_context
+from tests.support.asserts import assert_success
+
+
+def get_window_handles(session):
+ return session.transport.send(
+ "GET", "session/{session_id}/window/handles".format(**vars(session))
+ )
+
+
+def test_basic(session):
+ with using_context(session, "chrome"):
+ response = get_window_handles(session)
+ assert_success(response, session.handles)
+
+
+def test_different_handles_than_content_scope(session):
+ response = get_window_handles(session)
+ content_handles = assert_success(response)
+
+ with using_context(session, "chrome"):
+ response = get_window_handles(session)
+ chrome_handles = assert_success(response)
+
+ assert chrome_handles != content_handles
+ assert len(chrome_handles) == 1
+ assert len(content_handles) == 1
+
+
+def test_multiple_windows_and_tabs(session):
+ session.new_window(type_hint="window")
+ session.new_window(type_hint="tab")
+
+ response = get_window_handles(session)
+ content_handles = assert_success(response)
+
+ with using_context(session, "chrome"):
+ response = get_window_handles(session)
+ chrome_handles = assert_success(response)
+
+ assert chrome_handles != content_handles
+ assert len(chrome_handles) == 2
+ assert len(content_handles) == 3
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/bidi_disabled.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/bidi_disabled.py
new file mode 100644
index 0000000000..eeb5a18740
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/bidi_disabled.py
@@ -0,0 +1,33 @@
+from copy import deepcopy
+
+
+def test_marionette_fallback_webdriver_session(configuration, geckodriver):
+ config = deepcopy(configuration)
+ config["capabilities"]["webSocketUrl"] = True
+
+ prefs = config["capabilities"]["moz:firefoxOptions"].get("prefs", {})
+ prefs.update({"remote.active-protocols": 2})
+ config["capabilities"]["moz:firefoxOptions"]["prefs"] = prefs
+
+ try:
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ assert driver.session.capabilities.get("webSocketUrl") is None
+
+ # Sanity check that Marionette works as expected and by default returns
+ # at least one window handle
+ assert len(driver.session.handles) >= 1
+
+ finally:
+ driver.stop()
+
+ # WebDriver BiDi has to be re-enabled. Because we cannot easily
+ # get rid of the value let geckodriver overwrite it with the current
+ # default.
+ prefs.update({"remote.active-protocols": 3})
+
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ assert driver.session.capabilities.get("webSocketUrl") is not None
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/binary.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/binary.py
new file mode 100644
index 0000000000..79d1f842ed
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/binary.py
@@ -0,0 +1,33 @@
+import os
+
+from tests.support.asserts import assert_error, assert_success
+
+
+def test_bad_binary(new_session, configuration):
+ # skipif annotations are forbidden in wpt
+ if os.path.exists("/bin/echo"):
+ capabilities = configuration["capabilities"].copy()
+ capabilities["moz:firefoxOptions"]["binary"] = "/bin/echo"
+
+ response, _ = new_session({"capabilities": {"alwaysMatch": capabilities}})
+ assert_error(response, "invalid argument")
+
+
+def test_shell_script_binary(new_session, configuration):
+ # skipif annotations are forbidden in wpt
+ if os.path.exists("/bin/bash"):
+ capabilities = configuration["capabilities"].copy()
+ binary = configuration["browser"]["binary"]
+
+ path = os.path.abspath("firefox.sh")
+ assert not os.path.exists(path)
+ try:
+ script = f"""#!/bin/bash\n\n"{binary}" $@\n"""
+ with open("firefox.sh", "w") as f:
+ f.write(script)
+ os.chmod(path, 0o744)
+ capabilities["moz:firefoxOptions"]["binary"] = path
+ response, _ = new_session({"capabilities": {"alwaysMatch": capabilities}})
+ assert_success(response)
+ finally:
+ os.unlink(path)
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/conftest.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/conftest.py
new file mode 100644
index 0000000000..1cab6784c2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/conftest.py
@@ -0,0 +1,58 @@
+import pytest
+from webdriver.transport import HTTPWireProtocol
+
+
+@pytest.fixture(name="configuration")
+def fixture_configuration(configuration):
+ """Remove "acceptInsecureCerts" from capabilities if it exists.
+
+ Some browser configurations add acceptInsecureCerts capability by default.
+ Remove it during new_session tests to avoid interference.
+ """
+ if "acceptInsecureCerts" in configuration["capabilities"]:
+ configuration = dict(configuration)
+ del configuration["capabilities"]["acceptInsecureCerts"]
+ return configuration
+
+
+@pytest.fixture(name="new_session")
+def fixture_new_session(request, configuration, current_session):
+ """Start a new session for tests which themselves test creating new sessions.
+
+ :param body: The content of the body for the new session POST request.
+
+ :param delete_existing_session: Allows the fixture to delete an already
+ created custom session before the new session is getting created. This
+ is useful for tests which call this fixture multiple times within the
+ same test.
+ """
+ custom_session = {}
+
+ transport = HTTPWireProtocol(
+ configuration["host"],
+ configuration["port"],
+ url_prefix="/",
+ )
+
+ def _delete_session(session_id):
+ transport.send("DELETE", "session/{}".format(session_id))
+
+ def new_session(body, delete_existing_session=False, headers=None):
+ # If there is an active session from the global session fixture,
+ # delete that one first
+ if current_session is not None:
+ current_session.end()
+
+ if delete_existing_session:
+ _delete_session(custom_session["session"]["sessionId"])
+
+ response = transport.send("POST", "session", body, headers=headers)
+ if response.status == 200:
+ custom_session["session"] = response.body["value"]
+ return response, custom_session.get("session", None)
+
+ yield new_session
+
+ if custom_session.get("session") is not None:
+ _delete_session(custom_session["session"]["sessionId"])
+ custom_session = None
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/create.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/create.py
new file mode 100644
index 0000000000..9649b938ad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/create.py
@@ -0,0 +1,11 @@
+# META: timeout=long
+from tests.support.asserts import assert_success
+
+
+def test_valid_content_type(new_session, configuration):
+ headers = {"content-type": "application/json"}
+ response, _ = new_session(
+ {"capabilities": {"alwaysMatch": dict(configuration["capabilities"])}},
+ headers=headers,
+ )
+ assert_success(response)
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/invalid.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/invalid.py
new file mode 100644
index 0000000000..dc7a0caee9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/invalid.py
@@ -0,0 +1,53 @@
+from copy import deepcopy
+
+import pytest
+from tests.support.asserts import assert_error
+
+
+@pytest.mark.parametrize(
+ "headers",
+ [
+ {"origin": "http://localhost"},
+ {"origin": "http://localhost:8000"},
+ {"origin": "http://127.0.0.1"},
+ {"origin": "http://127.0.0.1:8000"},
+ {"origin": "null"},
+ {"ORIGIN": "https://example.org"},
+ {"host": "example.org:4444"},
+ {"Host": "example.org"},
+ {"host": "localhost:80"},
+ {"host": "localhost"},
+ {"content-type": "application/x-www-form-urlencoded"},
+ {"content-type": "multipart/form-data"},
+ {"content-type": "text/plain"},
+ {"Content-TYPE": "APPLICATION/x-www-form-urlencoded"},
+ {"content-type": "MULTIPART/FORM-DATA"},
+ {"CONTENT-TYPE": "TEXT/PLAIN"},
+ {"content-type": "text/plain ; charset=utf-8"},
+ {"content-type": "text/plain;foo"},
+ {"content-type": "text/PLAIN ; foo;charset=utf8"},
+ ],
+)
+def test_invalid(new_session, configuration, headers):
+ response, _ = new_session(
+ {"capabilities": {"alwaysMatch": dict(configuration["capabilities"])}},
+ headers=headers,
+ )
+ assert_error(response, "unknown error")
+
+
+@pytest.mark.parametrize(
+ "argument",
+ [
+ "--marionette",
+ "--remote-debugging-port",
+ "--remote-allow-hosts",
+ "--remote-allow-origins",
+ ],
+)
+def test_forbidden_arguments(configuration, new_session, argument):
+ capabilities = deepcopy(configuration["capabilities"])
+ capabilities["moz:firefoxOptions"]["args"] = [argument]
+
+ response, _ = new_session({"capabilities": {"alwaysMatch": capabilities}})
+ assert_error(response, "invalid argument")
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/new_session/profile_root.py b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/profile_root.py
new file mode 100644
index 0000000000..fc3607bed9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/new_session/profile_root.py
@@ -0,0 +1,43 @@
+import copy
+import os
+
+import pytest
+
+
+def test_profile_root(tmp_path, configuration, geckodriver, user_prefs):
+ profile_path = os.path.join(tmp_path, "geckodriver-test")
+ os.makedirs(profile_path)
+
+ config = copy.deepcopy(configuration)
+
+ # Pass all the wpt preferences from the default profile's user.js via
+ # capabilities to allow geckodriver to create a new valid profile itself.
+ config["capabilities"]["moz:firefoxOptions"]["prefs"] = user_prefs
+
+ # Ensure we don't set a profile in command line arguments
+ del config["capabilities"]["moz:firefoxOptions"]["args"]
+
+ extra_args = ["--profile-root", profile_path]
+
+ assert os.listdir(profile_path) == []
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+ driver.new_session()
+ assert len(os.listdir(profile_path)) == 1
+ driver.delete_session()
+ assert os.listdir(profile_path) == []
+
+
+def test_profile_root_missing(tmp_path, configuration, geckodriver):
+ profile_path = os.path.join(tmp_path, "missing-path")
+ assert not os.path.exists(profile_path)
+
+ config = copy.deepcopy(configuration)
+ # Ensure we don't set a profile in command line arguments
+ del config["capabilities"]["moz:firefoxOptions"]["args"]
+
+ extra_args = ["--profile-root", profile_path]
+
+ with pytest.raises(ChildProcessError) as exc_info:
+ geckodriver(config=config, extra_args=extra_args)
+ assert str(exc_info.value) == "geckodriver terminated with code 64"
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/perform_actions/wheel.py b/testing/web-platform/mozilla/tests/webdriver/classic/perform_actions/wheel.py
new file mode 100644
index 0000000000..6ca328f33b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/perform_actions/wheel.py
@@ -0,0 +1,37 @@
+from copy import deepcopy
+
+import pytest
+from tests.classic.perform_actions.support.refine import get_events
+
+
+@pytest.mark.parametrize("device_pixel_ratio", ["1.0", "2.0", "0.5"])
+def test_scroll_delta_device_pixel(configuration, url, geckodriver, device_pixel_ratio):
+ config = deepcopy(configuration)
+
+ prefs = config["capabilities"]["moz:firefoxOptions"].get("prefs", {})
+ prefs.update({"layout.css.devPixelsPerPx": device_pixel_ratio})
+ config["capabilities"]["moz:firefoxOptions"]["prefs"] = prefs
+
+ try:
+ driver = geckodriver(config=config)
+ driver.new_session()
+
+ driver.session.url = url(
+ "/webdriver/tests/support/html/test_actions_scroll.html"
+ )
+
+ target = driver.session.find.css("#scrollable", all=False)
+
+ chain = driver.session.actions.sequence("wheel", "wheel_id")
+ chain.scroll(0, 0, 5, 10, origin=target).perform()
+
+ events = get_events(driver.session)
+ assert len(events) == 1
+ assert events[0]["type"] == "wheel"
+ assert events[0]["deltaX"] == 5
+ assert events[0]["deltaY"] == 10
+ assert events[0]["deltaZ"] == 0
+ assert events[0]["target"] == "scrollable-content"
+
+ finally:
+ driver.stop()
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/protocol/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_hosts.py b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_hosts.py
new file mode 100644
index 0000000000..17ae2c2c68
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_hosts.py
@@ -0,0 +1,53 @@
+from copy import deepcopy
+
+import pytest
+from support.network import get_host, http_request, websocket_request
+
+
+@pytest.mark.parametrize(
+ "allow_hosts, hostname, port_type, status",
+ [
+ # Valid hosts
+ (["localhost.localdomain", "localhost"], "localhost", "server_port", 200),
+ (["localhost.localdomain", "localhost"], "127.0.0.1", "server_port", 200),
+ # Invalid hosts
+ (["localhost.localdomain"], "localhost", "server_port", 500),
+ (["localhost"], "localhost", "wrong_port", 500),
+ (["www.localhost"], "localhost", "server_port", 500),
+ ],
+)
+def test_allow_hosts(geckodriver, allow_hosts, hostname, port_type, status):
+ extra_args = ["--allow-hosts"] + allow_hosts
+
+ driver = geckodriver(hostname=hostname, extra_args=extra_args)
+ host = get_host(port_type, hostname, driver.port)
+ response = http_request(driver.hostname, driver.port, host=host)
+
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "allow_hosts, hostname, status",
+ [
+ (["mozilla.org", "testhost"], "testhost", 101),
+ (["mozilla.org"], "testhost", 400),
+ ],
+ ids=["allowed", "not allowed"],
+)
+def test_allow_hosts_passed_to_remote_agent(
+ configuration, geckodriver, allow_hosts, hostname, status
+):
+ config = deepcopy(configuration)
+ config["capabilities"]["webSocketUrl"] = True
+
+ extra_args = ["--allow-hosts"] + allow_hosts
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+
+ driver.new_session()
+
+ host = get_host("default_port", hostname, driver.remote_agent_port)
+ response = websocket_request("127.0.0.1", driver.remote_agent_port, host=host)
+ assert response.status == status
+
+ driver.delete_session()
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_origins.py b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_origins.py
new file mode 100644
index 0000000000..72b6fba482
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/allow_origins.py
@@ -0,0 +1,56 @@
+from copy import deepcopy
+
+import pytest
+from support.network import http_request, websocket_request
+
+
+@pytest.mark.parametrize(
+ "allow_origins, origin, status",
+ [
+ # Valid origins
+ (["http://web-platform.test"], "http://web-platform.test", 200),
+ (["http://web-platform.test"], "http://web-platform.test:80", 200),
+ (["https://web-platform.test"], "https://web-platform.test:443", 200),
+ # Invalid origins
+ (["https://web-platform.test"], "http://web-platform.test", 500),
+ (["http://web-platform.test:8000"], "http://web-platform.test", 500),
+ (["http://web-platform.test"], "http://www.web-platform.test", 500),
+ ],
+)
+def test_allow_hosts(configuration, geckodriver, allow_origins, origin, status):
+ extra_args = ["--allow-origins"] + allow_origins
+
+ driver = geckodriver(extra_args=extra_args)
+ response = http_request(driver.hostname, driver.port, origin=origin)
+
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "allow_origins, origin, status",
+ [
+ (
+ ["https://web-platform.test", "http://web-platform.test"],
+ "http://web-platform.test",
+ 101,
+ ),
+ (["https://web-platform.test"], "http://web-platform.test", 400),
+ ],
+ ids=["allowed", "not allowed"],
+)
+def test_allow_origins_passed_to_remote_agent(
+ configuration, geckodriver, allow_origins, origin, status
+):
+ config = deepcopy(configuration)
+ config["capabilities"]["webSocketUrl"] = True
+
+ extra_args = ["--allow-origins"] + allow_origins
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+
+ driver.new_session()
+
+ response = websocket_request("127.0.0.1", driver.remote_agent_port, origin=origin)
+ assert response.status == status
+
+ driver.delete_session()
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/protocol/marionette_port.py b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/marionette_port.py
new file mode 100644
index 0000000000..09951abc43
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/marionette_port.py
@@ -0,0 +1,41 @@
+import os
+from copy import deepcopy
+
+import pytest
+
+
+@pytest.mark.parametrize("port", ["0", "2828"], ids=["system allocated", "fixed"])
+def test_marionette_port(geckodriver, port):
+ extra_args = ["--marionette-port", port]
+
+ driver = geckodriver(extra_args=extra_args)
+ driver.new_session()
+ driver.delete_session()
+
+
+def test_marionette_port_outdated_active_port_file(
+ configuration, geckodriver, custom_profile
+):
+ config = deepcopy(configuration)
+ extra_args = ["--marionette-port", "0"]
+
+ # Prepare a Marionette active port file that contains a port which will
+ # never be used when requesting a system allocated port.
+ active_port_file = os.path.join(custom_profile.profile, "MarionetteActivePort")
+ with open(active_port_file, "wb") as f:
+ f.write(b"53")
+
+ config["capabilities"]["moz:firefoxOptions"]["args"] = [
+ "--profile",
+ custom_profile.profile,
+ ]
+
+ driver = geckodriver(config=config, extra_args=extra_args)
+
+ driver.new_session()
+ with open(active_port_file, "rb") as f:
+ assert f.readline() != b"53"
+
+ driver.delete_session()
+ with pytest.raises(FileNotFoundError):
+ open(active_port_file, "rb")
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/protocol/request.py b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/request.py
new file mode 100644
index 0000000000..ad99d6964d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/protocol/request.py
@@ -0,0 +1,72 @@
+import pytest
+from support.network import get_host, http_request
+
+
+@pytest.mark.parametrize(
+ "hostname, port_type, status",
+ [
+ # Valid hosts
+ ("localhost", "server_port", 200),
+ ("127.0.0.1", "server_port", 200),
+ ("[::1]", "server_port", 200),
+ ("192.168.8.1", "server_port", 200),
+ ("[fdf8:f535:82e4::53]", "server_port", 200),
+ # Invalid hosts
+ ("localhost", "default_port", 500),
+ ("127.0.0.1", "default_port", 500),
+ ("[::1]", "default_port", 500),
+ ("192.168.8.1", "default_port", 500),
+ ("[fdf8:f535:82e4::53]", "default_port", 500),
+ ("example.org", "server_port", 500),
+ ("example.org", "wrong_port", 500),
+ ("example.org", "default_port", 500),
+ ("localhost", "wrong_port", 500),
+ ("127.0.0.1", "wrong_port", 500),
+ ("[::1]", "wrong_port", 500),
+ ("192.168.8.1", "wrong_port", 500),
+ ("[fdf8:f535:82e4::53]", "wrong_port", 500),
+ ],
+ ids=[
+ # Valid hosts
+ "localhost with same port as server",
+ "127.0.0.1 (loopback) with same port as server",
+ "[::1] (ipv6 loopback) with same port as server",
+ "ipv4 address with same port as server",
+ "ipv6 address with same port as server",
+ # Invalid hosts
+ "localhost with default port",
+ "127.0.0.1 (loopback) with default port",
+ "[::1] (ipv6 loopback) with default port",
+ "ipv4 address with default port",
+ "ipv6 address with default port",
+ "random hostname with the same port as server",
+ "random hostname with a different port than server",
+ "random hostname with default port",
+ "localhost with a different port than server",
+ "127.0.0.1 (loopback) with a different port than server",
+ "[::1] (ipv6 loopback) with a different port than server",
+ "ipv4 address with a different port than server",
+ "ipv6 address with a different port than server",
+ ],
+)
+def test_host_header(configuration, hostname, port_type, status):
+ host = get_host(port_type, hostname, configuration["port"])
+ response = http_request(configuration["host"], configuration["port"], host=host)
+
+ assert response.status == status
+
+
+@pytest.mark.parametrize(
+ "origin, add_port, status",
+ [
+ (None, False, 200),
+ ("", False, 500),
+ ("sometext", False, 500),
+ ("http://localhost", True, 500),
+ ],
+)
+def test_origin_header(configuration, origin, add_port, status):
+ if add_port:
+ origin = f"{origin}:{configuration['port']}"
+ response = http_request(configuration["host"], configuration["port"], origin=origin)
+ assert response.status == status
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/send_alert_text.py b/testing/web-platform/mozilla/tests/webdriver/classic/send_alert_text.py
new file mode 100644
index 0000000000..60d6a02af0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/send_alert_text.py
@@ -0,0 +1,22 @@
+from tests.support.asserts import assert_error
+from tests.support.http_handlers.authentication import basic_authentication
+
+
+def send_alert_text(session, text=None):
+ return session.transport.send(
+ "POST",
+ "session/{session_id}/alert/text".format(**vars(session)),
+ {"text": text},
+ )
+
+
+def test_basic_auth_unsupported_operation(url, session):
+ """
+ Basic auth dialogues are not included in HTML's definition of
+ 'user prompts': those are limited to the 'simple dialogues'
+ such as window.alert(), window.prompt() et al. and the print
+ dialogue.
+ """
+ session.url = basic_authentication(url)
+ response = send_alert_text(session, "Federer")
+ assert_error(response, "unsupported operation")
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/__init__.py b/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/__init__.py
new file mode 100644
index 0000000000..11a8a58a0f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/__init__.py
@@ -0,0 +1,12 @@
+def document_dimensions(session):
+ return tuple(
+ session.execute_script(
+ """
+ const {devicePixelRatio} = window;
+ const width = document.documentElement.scrollWidth;
+ const height = document.documentElement.scrollHeight;
+
+ return [Math.floor(width * devicePixelRatio), Math.floor(height * devicePixelRatio)];
+ """
+ )
+ )
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/iframe.py b/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/iframe.py
new file mode 100644
index 0000000000..fc231f2e11
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/iframe.py
@@ -0,0 +1,47 @@
+import pytest
+from tests.support.asserts import assert_success
+from tests.support.image import png_dimensions
+
+from . import document_dimensions
+
+DEFAULT_CSS_STYLE = """
+ <style>
+ div, iframe {
+ display: block;
+ border: 1px solid blue;
+ width: 10em;
+ height: 10em;
+ }
+ </style>
+"""
+
+DEFAULT_CONTENT = "<div>Lorem ipsum dolor sit amet.</div>"
+
+
+def take_full_screenshot(session):
+ return session.transport.send(
+ "GET",
+ "/session/{session_id}/moz/screenshot/full".format(
+ session_id=session.session_id
+ ),
+ )
+
+
+@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
+def test_source_origin(session, url, domain, inline, iframe):
+ session.url = inline("""{0}{1}""".format(DEFAULT_CSS_STYLE, DEFAULT_CONTENT))
+
+ response = take_full_screenshot(session)
+ reference_screenshot = assert_success(response)
+ assert png_dimensions(reference_screenshot) == document_dimensions(session)
+
+ iframe_content = "<style>body {{ margin: 0; }}</style>{}".format(DEFAULT_CONTENT)
+ session.url = inline(
+ """{0}{1}""".format(DEFAULT_CSS_STYLE, iframe(iframe_content, domain=domain))
+ )
+
+ response = take_full_screenshot(session)
+ screenshot = assert_success(response)
+ assert png_dimensions(screenshot) == document_dimensions(session)
+
+ assert screenshot == reference_screenshot
diff --git a/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/screenshot.py b/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/screenshot.py
new file mode 100644
index 0000000000..02373afd57
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/classic/take_full_screenshot/screenshot.py
@@ -0,0 +1,51 @@
+from tests.support.asserts import assert_error, assert_png, assert_success
+from tests.support.image import png_dimensions
+
+from . import document_dimensions
+
+
+def take_full_screenshot(session):
+ return session.transport.send(
+ "GET",
+ "/session/{session_id}/moz/screenshot/full".format(
+ session_id=session.session_id
+ ),
+ )
+
+
+def test_no_browsing_context(session, closed_window):
+ response = take_full_screenshot(session)
+ assert_error(response, "no such window")
+
+
+def test_html_document(session, inline):
+ session.url = inline("<input>")
+
+ response = take_full_screenshot(session)
+ value = assert_success(response)
+ assert_png(value)
+ assert png_dimensions(value) == document_dimensions(session)
+
+
+def test_xhtml_document(session, inline):
+ session.url = inline('<input type="text" />', doctype="xhtml")
+
+ response = take_full_screenshot(session)
+ value = assert_success(response)
+ assert_png(value)
+ assert png_dimensions(value) == document_dimensions(session)
+
+
+def test_document_extends_beyond_viewport(session, inline):
+ session.url = inline(
+ """
+ <style>
+ body { min-height: 200vh }
+ </style>
+ """
+ )
+
+ response = take_full_screenshot(session)
+ value = assert_success(response)
+ assert_png(value)
+ assert png_dimensions(value) == document_dimensions(session)
diff --git a/testing/web-platform/mozilla/tests/webdriver/conftest.py b/testing/web-platform/mozilla/tests/webdriver/conftest.py
new file mode 100644
index 0000000000..d754b39e79
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/conftest.py
@@ -0,0 +1,15 @@
+import os
+import sys
+
+base = os.path.dirname(__file__)
+webdriver_path = os.path.abspath(
+ os.path.join(base, "..", "..", "..", "tests", "webdriver")
+)
+sys.path.insert(0, os.path.join(webdriver_path))
+
+pytest_plugins = [
+ "support.fixtures",
+ "tests.support.fixtures",
+ "tests.support.fixtures_bidi",
+ "tests.support.fixtures_http",
+]
diff --git a/testing/web-platform/mozilla/tests/webdriver/harness/__init__.py b/testing/web-platform/mozilla/tests/webdriver/harness/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/harness/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/harness/crash_content_process.py b/testing/web-platform/mozilla/tests/webdriver/harness/crash_content_process.py
new file mode 100644
index 0000000000..fec862f066
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/harness/crash_content_process.py
@@ -0,0 +1,6 @@
+import pytest
+
+
+@pytest.mark.capabilities({"pageLoadStrategy": "none"})
+def test_detect_crash(session):
+ session.url = "about:crashcontent"
diff --git a/testing/web-platform/mozilla/tests/webdriver/harness/crash_parent_process.py b/testing/web-platform/mozilla/tests/webdriver/harness/crash_parent_process.py
new file mode 100644
index 0000000000..9bacade93e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/harness/crash_parent_process.py
@@ -0,0 +1,6 @@
+import pytest
+
+
+@pytest.mark.capabilities({"pageLoadStrategy": "none"})
+def test_detect_crash(session):
+ session.url = "about:crashparent"
diff --git a/testing/web-platform/mozilla/tests/webdriver/harness/preferences.py b/testing/web-platform/mozilla/tests/webdriver/harness/preferences.py
new file mode 100644
index 0000000000..b5cf36bd5e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/harness/preferences.py
@@ -0,0 +1,6 @@
+from support.fixtures import get_pref
+
+
+def test_recommended_preferences(session):
+ has_recommended_prefs = get_pref(session, "remote.prefs.recommended")
+ assert has_recommended_prefs is True
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/__init__.py b/testing/web-platform/mozilla/tests/webdriver/support/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/__init__.py
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/context.py b/testing/web-platform/mozilla/tests/webdriver/support/context.py
new file mode 100644
index 0000000000..910b202075
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/context.py
@@ -0,0 +1,20 @@
+import contextlib
+
+
+def set_context(session, context):
+ session.send_session_command("POST", "moz/context", {"context": context})
+
+
+@contextlib.contextmanager
+def using_context(session, context):
+ orig_context = session.send_session_command("GET", "moz/context")
+ needs_change = context != orig_context
+
+ if needs_change:
+ set_context(session, context)
+
+ try:
+ yield
+ finally:
+ if needs_change:
+ set_context(session, orig_context)
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py b/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py
new file mode 100644
index 0000000000..e9dbf1cdfe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/fixtures.py
@@ -0,0 +1,418 @@
+import argparse
+import json
+import os
+import re
+import socket
+import subprocess
+import threading
+import time
+from contextlib import suppress
+from urllib.parse import urlparse
+
+import pytest
+import webdriver
+from mozprofile import Preferences, Profile
+from mozrunner import FirefoxRunner
+
+from .context import using_context
+
+
+def get_arg_value(arg_names, args):
+ """Get an argument value from a list of arguments
+
+ This assumes that argparse argument parsing is close enough to the target
+ to be compatible, at least with the set of inputs we have.
+
+ :param arg_names: - List of names for the argument e.g. ["--foo", "-f"]
+ :param args: - List of arguments to parse
+ :returns: - Optional string argument value
+ """
+ parser = argparse.ArgumentParser()
+ parser.add_argument(*arg_names, action="store", dest="value", default=None)
+ parsed, _ = parser.parse_known_args(args)
+ return parsed.value
+
+
+@pytest.fixture(scope="module")
+def browser(full_configuration):
+ """Start a Firefox instance without using geckodriver.
+
+ geckodriver will automatically use the --remote-allow-hosts and
+ --remote.allow.origins command line arguments.
+
+ Starting Firefox without geckodriver allows to set those command line arguments
+ as needed. The fixture method returns the browser instance that should be used
+ to connect to a RemoteAgent supported protocol (CDP, WebDriver BiDi).
+ """
+ current_browser = None
+
+ def _browser(use_bidi=False, use_cdp=False, extra_args=None, extra_prefs=None):
+ nonlocal current_browser
+
+ # If the requested preferences and arguments match the ones for the
+ # already started firefox, we can reuse the current firefox instance,
+ # return the instance immediately.
+ if current_browser:
+ if (
+ current_browser.use_bidi == use_bidi
+ and current_browser.use_cdp == use_cdp
+ and current_browser.extra_args == extra_args
+ and current_browser.extra_prefs == extra_prefs
+ and current_browser.is_running
+ ):
+ return current_browser
+
+ # Otherwise, if firefox is already started, terminate it because we need
+ # to create a new instance for the provided preferences.
+ current_browser.quit()
+
+ binary = full_configuration["browser"]["binary"]
+ env = full_configuration["browser"]["env"]
+ firefox_options = full_configuration["capabilities"]["moz:firefoxOptions"]
+ current_browser = Browser(
+ binary,
+ firefox_options,
+ use_bidi=use_bidi,
+ use_cdp=use_cdp,
+ extra_args=extra_args,
+ extra_prefs=extra_prefs,
+ env=env,
+ )
+ current_browser.start()
+ return current_browser
+
+ yield _browser
+
+ # Stop firefox at the end of the test module.
+ if current_browser is not None:
+ current_browser.quit()
+ current_browser = None
+
+
+@pytest.fixture
+def profile_folder(configuration):
+ firefox_options = configuration["capabilities"]["moz:firefoxOptions"]
+ return get_arg_value(["--profile"], firefox_options["args"])
+
+
+@pytest.fixture
+def custom_profile(profile_folder):
+ # Clone the known profile for automation preferences
+ profile = Profile.clone(profile_folder)
+
+ yield profile
+
+ profile.cleanup()
+
+
+@pytest.fixture
+def geckodriver(configuration):
+ """Start a geckodriver instance directly."""
+ driver = None
+
+ def _geckodriver(config=None, hostname=None, extra_args=None):
+ nonlocal driver
+
+ if config is None:
+ config = configuration
+
+ driver = Geckodriver(config, hostname, extra_args)
+ driver.start()
+
+ return driver
+
+ yield _geckodriver
+
+ if driver is not None:
+ driver.stop()
+
+
+@pytest.fixture
+def user_prefs(profile_folder):
+ user_js = os.path.join(profile_folder, "user.js")
+
+ prefs = {}
+ for pref_name, pref_value in Preferences().read_prefs(user_js):
+ prefs[pref_name] = pref_value
+
+ return prefs
+
+
+class Browser:
+ def __init__(
+ self,
+ binary,
+ firefox_options,
+ use_bidi=False,
+ use_cdp=False,
+ extra_args=None,
+ extra_prefs=None,
+ env=None,
+ ):
+ self.use_bidi = use_bidi
+ self.bidi_port_file = None
+ self.use_cdp = use_cdp
+ self.cdp_port_file = None
+ self.extra_args = extra_args
+ self.extra_prefs = extra_prefs
+
+ self.debugger_address = None
+ self.remote_agent_host = None
+ self.remote_agent_port = None
+
+ # Prepare temporary profile
+ _profile_arg, profile_folder = firefox_options["args"]
+ self.profile = Profile.clone(profile_folder)
+ if self.extra_prefs is not None:
+ self.profile.set_preferences(self.extra_prefs)
+
+ if use_cdp:
+ self.cdp_port_file = os.path.join(
+ self.profile.profile, "DevToolsActivePort"
+ )
+ with suppress(FileNotFoundError):
+ os.remove(self.cdp_port_file)
+ if use_bidi:
+ self.webdriver_bidi_file = os.path.join(
+ self.profile.profile, "WebDriverBiDiServer.json"
+ )
+ with suppress(FileNotFoundError):
+ os.remove(self.webdriver_bidi_file)
+
+ cmdargs = ["-no-remote"]
+ if self.use_bidi or self.use_cdp:
+ cmdargs.extend(["--remote-debugging-port", "0"])
+ if self.extra_args is not None:
+ cmdargs.extend(self.extra_args)
+ self.runner = FirefoxRunner(
+ binary=binary, profile=self.profile, cmdargs=cmdargs, env=env
+ )
+
+ @property
+ def is_running(self):
+ return self.runner.is_running()
+
+ def start(self):
+ # Start Firefox.
+ self.runner.start()
+
+ if self.use_bidi:
+ # Wait until the WebDriverBiDiServer.json file is ready
+ while not os.path.exists(self.webdriver_bidi_file):
+ time.sleep(0.1)
+
+ # Read the connection details from file
+ data = json.loads(open(self.webdriver_bidi_file).read())
+ self.remote_agent_host = data["ws_host"]
+ self.remote_agent_port = int(data["ws_port"])
+
+ if self.use_cdp:
+ # Wait until the DevToolsActivePort file is ready
+ while not os.path.exists(self.cdp_port_file):
+ time.sleep(0.1)
+
+ # Read the port if needed and the debugger address from the
+ # DevToolsActivePort file
+ lines = open(self.cdp_port_file).readlines()
+ assert len(lines) == 2
+
+ if self.remote_agent_port is None:
+ self.remote_agent_port = int(lines[0].strip())
+ self.debugger_address = lines[1].strip()
+
+ def quit(self, clean_profile=True):
+ if self.is_running:
+ self.runner.stop()
+ self.runner.cleanup()
+
+ if clean_profile:
+ self.profile.cleanup()
+
+ def wait(self):
+ if self.is_running is True:
+ self.runner.wait()
+
+
+class Geckodriver:
+ PORT_RE = re.compile(b".*Listening on [^ :]*:(\d+)")
+
+ def __init__(self, configuration, hostname=None, extra_args=None):
+ self.config = configuration["webdriver"]
+ self.requested_capabilities = configuration["capabilities"]
+ self.hostname = hostname or configuration["host"]
+ self.extra_args = extra_args or []
+ self.env = configuration["browser"]["env"]
+
+ self.command = None
+ self.proc = None
+ self.port = None
+ self.reader_thread = None
+
+ self.capabilities = {"alwaysMatch": self.requested_capabilities}
+ self.session = None
+
+ @property
+ def remote_agent_port(self):
+ webSocketUrl = self.session.capabilities.get("webSocketUrl")
+ assert webSocketUrl is not None
+
+ return urlparse(webSocketUrl).port
+
+ def start(self):
+ self.command = (
+ [self.config["binary"], "--port", "0"]
+ + self.config["args"]
+ + self.extra_args
+ )
+
+ print(f"Running command: {' '.join(self.command)}")
+ self.proc = subprocess.Popen(self.command, env=self.env, stdout=subprocess.PIPE)
+
+ self.reader_thread = threading.Thread(
+ target=readOutputLine,
+ args=(self.proc.stdout, self.processOutputLine),
+ daemon=True,
+ )
+ self.reader_thread.start()
+ # Wait for the port to become ready
+ end_time = time.time() + 10
+ while time.time() < end_time:
+ returncode = self.proc.poll()
+ if returncode is not None:
+ raise ChildProcessError(
+ f"geckodriver terminated with code {returncode}"
+ )
+ if self.port is not None:
+ with socket.socket() as sock:
+ if sock.connect_ex((self.hostname, self.port)) == 0:
+ break
+ else:
+ time.sleep(0.1)
+ else:
+ if self.port is None:
+ raise OSError(
+ f"Failed to read geckodriver port started on {self.hostname}"
+ )
+ raise ConnectionRefusedError(
+ f"Failed to connect to geckodriver on {self.hostname}:{self.port}"
+ )
+
+ self.session = webdriver.Session(
+ self.hostname, self.port, capabilities=self.capabilities
+ )
+
+ return self
+
+ def processOutputLine(self, line):
+ if self.port is None:
+ m = self.PORT_RE.match(line)
+ if m is not None:
+ self.port = int(m.groups()[0])
+
+ def stop(self):
+ if self.session is not None:
+ self.delete_session()
+ if self.proc:
+ self.proc.kill()
+ self.port = None
+ if self.reader_thread is not None:
+ self.reader_thread.join()
+
+ def new_session(self):
+ self.session.start()
+
+ def delete_session(self):
+ self.session.end()
+
+
+def readOutputLine(stream, callback):
+ while True:
+ line = stream.readline()
+ if not line:
+ break
+
+ callback(line)
+
+
+def clear_pref(session, pref):
+ """Clear the user-defined value from the specified preference.
+
+ :param pref: Name of the preference.
+ """
+ with using_context(session, "chrome"):
+ session.execute_script(
+ """
+ const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+ );
+ Preferences.reset(arguments[0]);
+ """,
+ args=(pref,),
+ )
+
+
+def get_pref(session, pref):
+ """Get the value of the specified preference.
+
+ :param pref: Name of the preference.
+ """
+ with using_context(session, "chrome"):
+ pref_value = session.execute_script(
+ """
+ const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+ );
+
+ let pref = arguments[0];
+
+ prefs = new Preferences();
+ return prefs.get(pref, null);
+ """,
+ args=(pref,),
+ )
+ return pref_value
+
+
+def set_pref(session, pref, value):
+ """Set the value of the specified preference.
+
+ :param pref: Name of the preference.
+ :param value: The value to set the preference to. If the value is None,
+ reset the preference to its default value. If no default
+ value exists, the preference will cease to exist.
+ """
+ if value is None:
+ clear_pref(session, pref)
+ return
+
+ with using_context(session, "chrome"):
+ session.execute_script(
+ """
+ const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+ );
+
+ const [pref, value] = arguments;
+
+ prefs = new Preferences();
+ prefs.set(pref, value);
+ """,
+ args=(pref, value),
+ )
+
+
+@pytest.fixture
+def use_pref(session):
+ """Set a specific pref value."""
+ reset_values = {}
+
+ def _use_pref(pref, value):
+ if pref not in reset_values:
+ reset_values[pref] = get_pref(session, pref)
+
+ set_pref(session, pref, value)
+
+ yield _use_pref
+
+ for pref, reset_value in reset_values.items():
+ set_pref(session, pref, reset_value)
diff --git a/testing/web-platform/mozilla/tests/webdriver/support/network.py b/testing/web-platform/mozilla/tests/webdriver/support/network.py
new file mode 100644
index 0000000000..85d0fa1094
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webdriver/support/network.py
@@ -0,0 +1,57 @@
+from http.client import HTTPConnection
+
+
+def websocket_request(
+ remote_agent_host, remote_agent_port, host=None, origin=None, path="/session"
+):
+ real_host = f"{remote_agent_host}:{remote_agent_port}"
+ url = f"http://{real_host}{path}"
+
+ conn = HTTPConnection(real_host)
+
+ skip_host = host is not None
+ conn.putrequest("GET", url, skip_host)
+
+ if host is not None:
+ conn.putheader("Host", host)
+
+ if origin is not None:
+ conn.putheader("Origin", origin)
+
+ conn.putheader("Connection", "upgrade")
+ conn.putheader("Upgrade", "websocket")
+ conn.putheader("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
+ conn.putheader("Sec-WebSocket-Version", "13")
+
+ conn.endheaders()
+
+ return conn.getresponse()
+
+
+def http_request(server_host, server_port, path="/status", host=None, origin=None):
+ url = f"http://{server_host}:{server_port}{path}"
+
+ conn = HTTPConnection(server_host, server_port)
+
+ custom_host = host is not None
+ conn.putrequest("GET", url, skip_host=custom_host)
+ if custom_host:
+ conn.putheader("Host", host)
+
+ if origin is not None:
+ conn.putheader("Origin", origin)
+
+ conn.endheaders()
+
+ return conn.getresponse()
+
+
+def get_host(port_type, hostname, server_port):
+ if port_type == "default_port":
+ return hostname
+ if port_type == "server_port":
+ return f"{hostname}:{server_port}"
+ if port_type == "wrong_port":
+ wrong_port = int(server_port) + 1
+ return f"{hostname}:{wrong_port}"
+ raise Exception(f"Unrecognised port_type {port_type}")
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/data_cache.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/data_cache.js
new file mode 100644
index 0000000000..74d5af8cc2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/data_cache.js
@@ -0,0 +1,175 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Utilities to improve the performance of the CTS, by caching data that is
+ * expensive to build using a two-level cache (in-memory, pre-computed file).
+ */import { assert } from '../util/util.js';
+
+
+
+
+
+/** Logger is a basic debug logger function */
+
+
+/**
+ * DataCacheNode represents a single cache entry in the LRU DataCache.
+ * DataCacheNode is a doubly linked list, so that least-recently-used entries can be removed, and
+ * cache hits can move the node to the front of the list.
+ */
+class DataCacheNode {
+ constructor(path, data) {
+ this.path = path;
+ this.data = data;
+ }
+
+ /** insertAfter() re-inserts this node in the doubly-linked list after `prev` */
+ insertAfter(prev) {
+ this.unlink();
+ this.next = prev.next;
+ this.prev = prev;
+ prev.next = this;
+ if (this.next) {
+ this.next.prev = this;
+ }
+ }
+
+ /** unlink() removes this node from the doubly-linked list */
+ unlink() {
+ const prev = this.prev;
+ const next = this.next;
+ if (prev) {
+ prev.next = next;
+ }
+ if (next) {
+ next.prev = prev;
+ }
+ this.prev = null;
+ this.next = null;
+ }
+
+ // The file path this node represents
+ // The deserialized data for this node
+ prev = null; // The previous node in the doubly-linked list
+ next = null; // The next node in the doubly-linked list
+}
+
+/** DataCache is an interface to a LRU-cached data store used to hold data cached by path */
+export class DataCache {
+ constructor() {
+ this.lruHeadNode.next = this.lruTailNode;
+ this.lruTailNode.prev = this.lruHeadNode;
+ }
+
+ /** setDataStore() sets the backing data store used by the data cache */
+ setStore(dataStore) {
+ this.dataStore = dataStore;
+ }
+
+ /** setDebugLogger() sets the verbose logger */
+ setDebugLogger(logger) {
+ this.debugLogger = logger;
+ }
+
+ /**
+ * fetch() retrieves cacheable data from the data cache, first checking the
+ * in-memory cache, then the data store (if specified), then resorting to
+ * building the data and storing it in the cache.
+ */
+ async fetch(cacheable) {
+ {
+ // First check the in-memory cache
+ const node = this.cache.get(cacheable.path);
+ if (node !== undefined) {
+ this.log('in-memory cache hit');
+ node.insertAfter(this.lruHeadNode);
+ return Promise.resolve(node.data);
+ }
+ }
+ this.log('in-memory cache miss');
+ // In in-memory cache miss.
+ // Next, try the data store.
+ if (this.dataStore !== null && !this.unavailableFiles.has(cacheable.path)) {
+ let serialized;
+ try {
+ serialized = await this.dataStore.load(cacheable.path);
+ this.log('loaded serialized');
+ } catch (err) {
+ // not found in data store
+ this.log(`failed to load (${cacheable.path}): ${err}`);
+ this.unavailableFiles.add(cacheable.path);
+ }
+ if (serialized !== undefined) {
+ this.log(`deserializing`);
+ const data = cacheable.deserialize(serialized);
+ this.addToCache(cacheable.path, data);
+ return data;
+ }
+ }
+ // Not found anywhere. Build the data, and cache for future lookup.
+ this.log(`cache: building (${cacheable.path})`);
+ const data = await cacheable.build();
+ this.addToCache(cacheable.path, data);
+ return data;
+ }
+
+ /**
+ * addToCache() creates a new node for `path` and `data`, inserting the new node at the front of
+ * the doubly-linked list. If the number of entries in the cache exceeds this.maxCount, then the
+ * least recently used entry is evicted
+ * @param path the file path for the data
+ * @param data the deserialized data
+ */
+ addToCache(path, data) {
+ if (this.cache.size >= this.maxCount) {
+ const toEvict = this.lruTailNode.prev;
+ assert(toEvict !== null);
+ toEvict.unlink();
+ this.cache.delete(toEvict.path);
+ this.log(`evicting ${toEvict.path}`);
+ }
+ const node = new DataCacheNode(path, data);
+ node.insertAfter(this.lruHeadNode);
+ this.cache.set(path, node);
+ this.log(`added ${path}. new count: ${this.cache.size}`);
+ }
+
+ log(msg) {
+ if (this.debugLogger !== null) {
+ this.debugLogger(`DataCache: ${msg}`);
+ }
+ }
+
+ // Max number of entries in the cache before LRU entries are evicted.
+ maxCount = 4;
+
+ cache = new Map();
+ lruHeadNode = new DataCacheNode('', null); // placeholder node (no path or data)
+ lruTailNode = new DataCacheNode('', null); // placeholder node (no path or data)
+ unavailableFiles = new Set();
+ dataStore = null;
+ debugLogger = null;
+}
+
+/** The data cache */
+export const dataCache = new DataCache();
+
+/** true if the current process is building the cache */
+let isBuildingDataCache = false;
+
+/** @returns true if the data cache is currently being built */
+export function getIsBuildingDataCache() {
+ return isBuildingDataCache;
+}
+
+/** Sets whether the data cache is currently being built */
+export function setIsBuildingDataCache(value = true) {
+ isBuildingDataCache = value;
+}
+
+/**
+ * Cacheable is the interface to something that can be stored into the
+ * DataCache.
+ * The 'npm run gen_cache' tool will look for module-scope variables of this
+ * interface, with the name `d`.
+ */ \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js
new file mode 100644
index 0000000000..d64245f5f8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/fixture.js
@@ -0,0 +1,358 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../util/util.js';
+
+export class SkipTestCase extends Error {}
+export class UnexpectedPassError extends Error {}
+
+export { TestCaseRecorder } from '../internal/logging/test_case_recorder.js';
+
+/** The fully-general type for params passed to a test function invocation. */
+
+
+
+
+
+
+
+
+
+export class SubcaseBatchState {
+ constructor(
+ recorder,
+ /** The case parameters for this test fixture shared state. Subcase params are not included. */
+ params)
+ {this.recorder = recorder;this.params = params;}
+
+ /**
+ * Runs before the `.before()` function.
+ * @internal MAINTENANCE_TODO: Make this not visible to test code?
+ */
+ async init() {}
+ /**
+ * Runs between the `.before()` function and the subcases.
+ * @internal MAINTENANCE_TODO: Make this not visible to test code?
+ */
+ async postInit() {}
+ /**
+ * Runs after all subcases finish.
+ * @internal MAINTENANCE_TODO: Make this not visible to test code?
+ */
+ async finalize() {}
+}
+
+/**
+ * A Fixture is a class used to instantiate each test sub/case at run time.
+ * A new instance of the Fixture is created for every single test subcase
+ * (i.e. every time the test function is run).
+ */
+export class Fixture {
+
+
+ /**
+ * Interface for recording logs and test status.
+ *
+ * @internal
+ */
+
+ eventualExpectations = [];
+ numOutstandingAsyncExpectations = 0;
+ objectsToCleanUp = [];
+
+ static MakeSharedState(recorder, params) {
+ return new SubcaseBatchState(recorder, params);
+ }
+
+ /** @internal */
+ constructor(sharedState, rec, params) {
+ this._sharedState = sharedState;
+ this.rec = rec;
+ this._params = params;
+ }
+
+ /**
+ * Returns the (case+subcase) parameters for this test function invocation.
+ */
+ get params() {
+ return this._params;
+ }
+
+ /**
+ * Gets the test fixture's shared state. This object is shared between subcases
+ * within the same testcase.
+ */
+ get sharedState() {
+ return this._sharedState;
+ }
+
+ /**
+ * Override this to do additional pre-test-function work in a derived fixture.
+ * This has to be a member function instead of an async `createFixture` function, because
+ * we need to be able to ergonomically override it in subclasses.
+ *
+ * @internal MAINTENANCE_TODO: Make this not visible to test code?
+ */
+ async init() {}
+
+ /**
+ * Override this to do additional post-test-function work in a derived fixture.
+ *
+ * Called even if init was unsuccessful.
+ *
+ * @internal MAINTENANCE_TODO: Make this not visible to test code?
+ */
+ async finalize() {
+ assert(
+ this.numOutstandingAsyncExpectations === 0,
+ 'there were outstanding immediateAsyncExpectations (e.g. expectUncapturedError) at the end of the test'
+ );
+
+ // Loop to exhaust the eventualExpectations in case they chain off each other.
+ while (this.eventualExpectations.length) {
+ const p = this.eventualExpectations.shift();
+ try {
+ await p;
+ } catch (ex) {
+ this.rec.threw(ex);
+ }
+ }
+
+ // And clean up any objects now that they're done being used.
+ for (const o of this.objectsToCleanUp) {
+ if ('getExtension' in o) {
+ const WEBGL_lose_context = o.getExtension('WEBGL_lose_context');
+ if (WEBGL_lose_context) WEBGL_lose_context.loseContext();
+ } else if ('destroy' in o) {
+ o.destroy();
+ } else {
+ o.close();
+ }
+ }
+ }
+
+ /**
+ * Tracks an object to be cleaned up after the test finishes.
+ *
+ * MAINTENANCE_TODO: Use this in more places. (Will be easier once .destroy() is allowed on
+ * invalid objects.)
+ */
+ trackForCleanup(o) {
+ this.objectsToCleanUp.push(o);
+ return o;
+ }
+
+ /** Tracks an object, if it's destroyable, to be cleaned up after the test finishes. */
+ tryTrackForCleanup(o) {
+ if (typeof o === 'object' && o !== null) {
+ if (
+ 'destroy' in o ||
+ 'close' in o ||
+ o instanceof WebGLRenderingContext ||
+ o instanceof WebGL2RenderingContext)
+ {
+ this.objectsToCleanUp.push(o);
+ }
+ }
+ return o;
+ }
+
+ /** Log a debug message. */
+ debug(msg) {
+ this.rec.debug(new Error(msg));
+ }
+
+ /** Throws an exception marking the subcase as skipped. */
+ skip(msg) {
+ throw new SkipTestCase(msg);
+ }
+
+ /** Throws an exception marking the subcase as skipped if condition is true */
+ skipIf(cond, msg = '') {
+ if (cond) {
+ this.skip(typeof msg === 'function' ? msg() : msg);
+ }
+ }
+
+ /** Log a warning and increase the result status to "Warn". */
+ warn(msg) {
+ this.rec.warn(new Error(msg));
+ }
+
+ /** Log an error and increase the result status to "ExpectFailed". */
+ fail(msg) {
+ this.rec.expectationFailed(new Error(msg));
+ }
+
+ /**
+ * Wraps an async function. Tracks its status to fail if the test tries to report a test status
+ * before the async work has finished.
+ */
+ async immediateAsyncExpectation(fn) {
+ this.numOutstandingAsyncExpectations++;
+ const ret = await fn();
+ this.numOutstandingAsyncExpectations--;
+ return ret;
+ }
+
+ /**
+ * Wraps an async function, passing it an `Error` object recording the original stack trace.
+ * The async work will be implicitly waited upon before reporting a test status.
+ */
+ eventualAsyncExpectation(fn) {
+ const promise = fn(new Error());
+ this.eventualExpectations.push(promise);
+ }
+
+ expectErrorValue(expectedError, ex, niceStack) {
+ if (!(ex instanceof Error)) {
+ niceStack.message = `THREW non-error value, of type ${typeof ex}: ${ex}`;
+ this.rec.expectationFailed(niceStack);
+ return;
+ }
+ const actualName = ex.name;
+ if (expectedError !== true && actualName !== expectedError) {
+ niceStack.message = `THREW ${actualName}, instead of ${expectedError}: ${ex}`;
+ this.rec.expectationFailed(niceStack);
+ } else {
+ niceStack.message = `OK: threw ${actualName}: ${ex.message}`;
+ this.rec.debug(niceStack);
+ }
+ }
+
+ /** Expect that the provided promise resolves (fulfills). */
+ shouldResolve(p, msg) {
+ this.eventualAsyncExpectation(async (niceStack) => {
+ const m = msg ? ': ' + msg : '';
+ try {
+ await p;
+ niceStack.message = 'resolved as expected' + m;
+ } catch (ex) {
+ niceStack.message = `REJECTED${m}`;
+ if (ex instanceof Error) {
+ niceStack.message += '\n' + ex.message;
+ }
+ this.rec.expectationFailed(niceStack);
+ }
+ });
+ }
+
+ /** Expect that the provided promise rejects, with the provided exception name. */
+ shouldReject(
+ expectedName,
+ p,
+ { allowMissingStack = false, message } = {})
+ {
+ this.eventualAsyncExpectation(async (niceStack) => {
+ const m = message ? ': ' + message : '';
+ try {
+ await p;
+ niceStack.message = 'DID NOT REJECT' + m;
+ this.rec.expectationFailed(niceStack);
+ } catch (ex) {
+ this.expectErrorValue(expectedName, ex, niceStack);
+ if (!allowMissingStack) {
+ if (!(ex instanceof Error && typeof ex.stack === 'string')) {
+ const exMessage = ex instanceof Error ? ex.message : '?';
+ niceStack.message = `rejected as expected, but missing stack (${exMessage})${m}`;
+ this.rec.expectationFailed(niceStack);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Expect that the provided function throws (if `true` or `string`) or not (if `false`).
+ * If a string is provided, expect that the throw exception has that name.
+ *
+ * MAINTENANCE_TODO: Change to `string | false` so the exception name is always checked.
+ */
+ shouldThrow(
+ expectedError,
+ fn,
+ { allowMissingStack = false, message } = {})
+ {
+ const m = message ? ': ' + message : '';
+ try {
+ fn();
+ if (expectedError === false) {
+ this.rec.debug(new Error('did not throw, as expected' + m));
+ } else {
+ this.rec.expectationFailed(new Error('unexpectedly did not throw' + m));
+ }
+ } catch (ex) {
+ if (expectedError === false) {
+ this.rec.expectationFailed(new Error('threw unexpectedly' + m));
+ } else {
+ this.expectErrorValue(expectedError, ex, new Error(m));
+ if (!allowMissingStack) {
+ if (!(ex instanceof Error && typeof ex.stack === 'string')) {
+ this.rec.expectationFailed(new Error('threw as expected, but missing stack' + m));
+ }
+ }
+ }
+ }
+ }
+
+ /** Expect that a condition is true. */
+ expect(cond, msg) {
+ if (cond) {
+ const m = msg ? ': ' + msg : '';
+ this.rec.debug(new Error('expect OK' + m));
+ } else {
+ this.rec.expectationFailed(new Error(msg));
+ }
+ return cond;
+ }
+
+ /**
+ * If the argument is an `Error`, fail (or warn). If it's `undefined`, no-op.
+ * If the argument is an array, apply the above behavior on each of elements.
+ */
+ expectOK(
+ error,
+ { mode = 'fail', niceStack } = {})
+ {
+ const handleError = (error) => {
+ if (error instanceof Error) {
+ if (niceStack) {
+ error.stack = niceStack.stack;
+ }
+ if (mode === 'fail') {
+ this.rec.expectationFailed(error);
+ } else if (mode === 'warn') {
+ this.rec.warn(error);
+ } else {
+ unreachable();
+ }
+ }
+ };
+
+ if (Array.isArray(error)) {
+ for (const e of error) {
+ handleError(e);
+ }
+ } else {
+ handleError(error);
+ }
+ }
+
+ eventualExpectOK(
+ error,
+ { mode = 'fail' } = {})
+ {
+ this.eventualAsyncExpectation(async (niceStack) => {
+ this.expectOK(await error, { mode, niceStack });
+ });
+ }
+}
+
+
+
+/**
+ * FixtureClass encapsulates a constructor for fixture and a corresponding
+ * shared state factory function. An interface version of the type is also
+ * defined for mixin declaration use ONLY. The interface version is necessary
+ * because mixin classes need a constructor with a single any[] rest
+ * parameter.
+ */ \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/metadata.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/metadata.js
new file mode 100644
index 0000000000..7742a9c1ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/metadata.js
@@ -0,0 +1,28 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../util/util.js'; /** Metadata about tests (that can't be derived at runtime). */
+
+
+
+
+
+
+
+
+
+
+
+
+
+export function loadMetadataForSuite(suiteDir) {
+ assert(typeof require !== 'undefined', 'loadMetadataForSuite is only implemented on Node');
+ const fs = require('fs');
+
+ const metadataFile = `${suiteDir}/listing_meta.json`;
+ if (!fs.existsSync(metadataFile)) {
+ return null;
+ }
+
+ const metadata = JSON.parse(fs.readFileSync(metadataFile, 'utf8'));
+ return metadata;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/params_builder.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/params_builder.js
new file mode 100644
index 0000000000..d3d0d45880
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/params_builder.js
@@ -0,0 +1,389 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { mergeParams, mergeParamsChecked } from '../internal/params_utils.js';import { comparePublicParamsPaths, Ordering } from '../internal/query/compare.js';import { stringifyPublicParams } from '../internal/query/stringify_params.js';
+
+import { assert, mapLazy, objectEquals } from '../util/util.js';
+
+
+
+// ================================================================
+// "Public" ParamsBuilder API / Documentation
+// ================================================================
+
+/**
+ * Provides doc comments for the methods of CaseParamsBuilder and SubcaseParamsBuilder.
+ * (Also enforces rough interface match between them.)
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Determines the resulting parameter object type which would be generated by an object of
+ * the given ParamsBuilder type.
+ */
+
+
+
+
+
+
+
+
+
+// ================================================================
+// Implementation
+// ================================================================
+
+/**
+ * Iterable over pairs of either:
+ * - `[case params, Iterable<subcase params>]` if there are subcases.
+ * - `[case params, undefined]` if not.
+ */
+
+
+
+
+/**
+ * Base class for `CaseParamsBuilder` and `SubcaseParamsBuilder`.
+ */
+export class ParamsBuilderBase {
+
+
+ constructor(cases) {
+ this.cases = cases;
+ }
+
+ /**
+ * Hidden from test files. Use `builderIterateCasesWithSubcases` to access this.
+ */
+
+
+
+}
+
+/**
+ * Calls the (normally hidden) `iterateCasesWithSubcases()` method.
+ */
+export function builderIterateCasesWithSubcases(
+builder,
+caseFilter)
+{
+
+
+
+
+ return builder.iterateCasesWithSubcases(caseFilter);
+}
+
+/**
+ * Builder for combinatorial test **case** parameters.
+ *
+ * CaseParamsBuilder is immutable. Each method call returns a new, immutable object,
+ * modifying the list of cases according to the method called.
+ *
+ * This means, for example, that the `unit` passed into `TestBuilder.params()` can be reused.
+ */
+export class CaseParamsBuilder extends
+ParamsBuilderBase
+
+{
+ *iterateCasesWithSubcases(caseFilter) {
+ for (const caseP of this.cases(caseFilter)) {
+ if (caseFilter) {
+ // this.cases() only filters out cases which conflict with caseFilter. Now that we have
+ // the final caseP, filter out cases which are missing keys that caseFilter requires.
+ const ordering = comparePublicParamsPaths(caseP, caseFilter);
+ if (ordering === Ordering.StrictSuperset || ordering === Ordering.Unordered) {
+ continue;
+ }
+ }
+
+ yield [caseP, undefined];
+ }
+ }
+
+ [Symbol.iterator]() {
+ return this.cases(null);
+ }
+
+ /** @inheritDoc */
+ expandWithParams(
+ expander)
+ {
+ const baseGenerator = this.cases;
+ return new CaseParamsBuilder(function* (caseFilter) {
+ for (const a of baseGenerator(caseFilter)) {
+ for (const b of expander(a)) {
+ if (caseFilter) {
+ // If the expander generated any key-value pair that conflicts with caseFilter, skip.
+ const kvPairs = Object.entries(b);
+ if (kvPairs.some(([k, v]) => k in caseFilter && !objectEquals(caseFilter[k], v))) {
+ continue;
+ }
+ }
+
+ yield mergeParamsChecked(a, b);
+ }
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ expand(
+ key,
+ expander)
+ {
+ const baseGenerator = this.cases;
+ return new CaseParamsBuilder(function* (caseFilter) {
+ for (const a of baseGenerator(caseFilter)) {
+ assert(!(key in a), `New key '${key}' already exists in ${JSON.stringify(a)}`);
+
+ for (const v of expander(a)) {
+ // If the expander generated a value for this key that conflicts with caseFilter, skip.
+ if (caseFilter && key in caseFilter) {
+ if (!objectEquals(caseFilter[key], v)) {
+ continue;
+ }
+ }
+ yield { ...a, [key]: v };
+ }
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ combineWithParams(
+ newParams)
+ {
+ assertNotGenerator(newParams);
+ const seenValues = new Set();
+ for (const params of newParams) {
+ const paramsStr = stringifyPublicParams(params);
+ assert(!seenValues.has(paramsStr), `Duplicate entry in combine[WithParams]: ${paramsStr}`);
+ seenValues.add(paramsStr);
+ }
+
+ return this.expandWithParams(() => newParams);
+ }
+
+ /** @inheritDoc */
+ combine(
+ key,
+ values)
+ {
+ assertNotGenerator(values);
+ const mapped = mapLazy(values, (v) => ({ [key]: v }));
+ return this.combineWithParams(mapped);
+ }
+
+ /** @inheritDoc */
+ filter(pred) {
+ const baseGenerator = this.cases;
+ return new CaseParamsBuilder(function* (caseFilter) {
+ for (const a of baseGenerator(caseFilter)) {
+ if (pred(a)) yield a;
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ unless(pred) {
+ return this.filter((x) => !pred(x));
+ }
+
+ /**
+ * "Finalize" the list of cases and begin defining subcases.
+ * Returns a new SubcaseParamsBuilder. Methods called on SubcaseParamsBuilder
+ * generate new subcases instead of new cases.
+ */
+ beginSubcases() {
+ return new SubcaseParamsBuilder(this.cases, function* () {
+ yield {};
+ });
+ }
+}
+
+/**
+ * The unit CaseParamsBuilder, representing a single case with no params: `[ {} ]`.
+ *
+ * `punit` is passed to every `.params()`/`.paramsSubcasesOnly()` call, so `kUnitCaseParamsBuilder`
+ * is only explicitly needed if constructing a ParamsBuilder outside of a test builder.
+ */
+export const kUnitCaseParamsBuilder = new CaseParamsBuilder(function* () {
+ yield {};
+});
+
+/**
+ * Builder for combinatorial test _subcase_ parameters.
+ *
+ * SubcaseParamsBuilder is immutable. Each method call returns a new, immutable object,
+ * modifying the list of subcases according to the method called.
+ */
+export class SubcaseParamsBuilder extends
+ParamsBuilderBase
+
+{
+
+
+ constructor(
+ cases,
+ generator)
+ {
+ super(cases);
+ this.subcases = generator;
+ }
+
+ *iterateCasesWithSubcases(caseFilter) {
+ for (const caseP of this.cases(caseFilter)) {
+ if (caseFilter) {
+ // this.cases() only filters out cases which conflict with caseFilter. Now that we have
+ // the final caseP, filter out cases which are missing keys that caseFilter requires.
+ const ordering = comparePublicParamsPaths(caseP, caseFilter);
+ if (ordering === Ordering.StrictSuperset || ordering === Ordering.Unordered) {
+ continue;
+ }
+ }
+
+ const subcases = Array.from(this.subcases(caseP));
+ if (subcases.length) {
+ yield [
+ caseP,
+ subcases];
+
+ }
+ }
+ }
+
+ /** @inheritDoc */
+ expandWithParams(
+ expander)
+ {
+ const baseGenerator = this.subcases;
+ return new SubcaseParamsBuilder(this.cases, function* (base) {
+ for (const a of baseGenerator(base)) {
+ for (const b of expander(mergeParams(base, a))) {
+ yield mergeParamsChecked(a, b);
+ }
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ expand(
+ key,
+ expander)
+ {
+ const baseGenerator = this.subcases;
+ return new SubcaseParamsBuilder(this.cases, function* (base) {
+ for (const a of baseGenerator(base)) {
+ const before = mergeParams(base, a);
+ assert(!(key in before), () => `Key '${key}' already exists in ${JSON.stringify(before)}`);
+
+ for (const v of expander(before)) {
+ yield { ...a, [key]: v };
+ }
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ combineWithParams(
+ newParams)
+ {
+ assertNotGenerator(newParams);
+ return this.expandWithParams(() => newParams);
+ }
+
+ /** @inheritDoc */
+ combine(
+ key,
+ values)
+ {
+ assertNotGenerator(values);
+ return this.expand(key, () => values);
+ }
+
+ /** @inheritDoc */
+ filter(pred) {
+ const baseGenerator = this.subcases;
+ return new SubcaseParamsBuilder(this.cases, function* (base) {
+ for (const a of baseGenerator(base)) {
+ if (pred(mergeParams(base, a))) yield a;
+ }
+ });
+ }
+
+ /** @inheritDoc */
+ unless(pred) {
+ return this.filter((x) => !pred(x));
+ }
+}
+
+/** Assert an object is not a Generator (a thing returned from a generator function). */
+function assertNotGenerator(x) {
+ if ('constructor' in x) {
+ assert(
+ x.constructor !== function* () {}().constructor,
+ 'Argument must not be a generator, as generators are not reusable'
+ );
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/resources.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/resources.js
new file mode 100644
index 0000000000..aa6ac2c1d5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/resources.js
@@ -0,0 +1,110 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Base path for resources. The default value is correct for non-worker WPT, but standalone and
+ * workers must access resources using a different base path, so this is overridden in
+ * `test_worker-worker.ts` and `standalone.ts`.
+ */let baseResourcePath = './resources';let crossOriginHost = '';
+
+function getAbsoluteBaseResourcePath(path) {
+ // Path is already an absolute one.
+ if (path[0] === '/') {
+ return path;
+ }
+
+ // Path is relative
+ const relparts = window.location.pathname.split('/');
+ relparts.pop();
+ const pathparts = path.split('/');
+
+ let i;
+ for (i = 0; i < pathparts.length; ++i) {
+ switch (pathparts[i]) {
+ case '':
+ break;
+ case '.':
+ break;
+ case '..':
+ relparts.pop();
+ break;
+ default:
+ relparts.push(pathparts[i]);
+ break;
+ }
+ }
+
+ return relparts.join('/');
+}
+
+function runningOnLocalHost() {
+ const hostname = window.location.hostname;
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
+}
+
+/**
+ * Get a path to a resource in the `resources` directory relative to the current execution context
+ * (html file or worker .js file), for `fetch()`, `<img>`, `<video>`, etc but from cross origin host.
+ * Provide onlineUrl if the case running online.
+ * @internal MAINTENANCE_TODO: Cases may run in the LAN environment (not localhost but no internet
+ * access). We temporarily use `crossOriginHost` to configure the cross origin host name in that situation.
+ * But opening to auto-detect mechanism or other solutions.
+ */
+export function getCrossOriginResourcePath(pathRelativeToResourcesDir, onlineUrl = '') {
+ // A cross origin host has been configured. Use this to load resource.
+ if (crossOriginHost !== '') {
+ return (
+ crossOriginHost +
+ getAbsoluteBaseResourcePath(baseResourcePath) +
+ '/' +
+ pathRelativeToResourcesDir);
+
+ }
+
+ // Using 'localhost' and '127.0.0.1' trick to load cross origin resource. Set cross origin host name
+ // to 'localhost' if case is not running in 'localhost' domain. Otherwise, use '127.0.0.1'.
+ // host name to locahost unless the server running in
+ if (runningOnLocalHost()) {
+ let crossOriginHostName = '';
+ if (location.hostname === 'localhost') {
+ crossOriginHostName = 'http://127.0.0.1';
+ } else {
+ crossOriginHostName = 'http://localhost';
+ }
+
+ return (
+ crossOriginHostName +
+ ':' +
+ location.port +
+ getAbsoluteBaseResourcePath(baseResourcePath) +
+ '/' +
+ pathRelativeToResourcesDir);
+
+ }
+
+ return onlineUrl;
+}
+
+/**
+ * Get a path to a resource in the `resources` directory, relative to the current execution context
+ * (html file or worker .js file), for `fetch()`, `<img>`, `<video>`, etc. Pass the cross origin host
+ * name if wants to load resoruce from cross origin host.
+ */
+export function getResourcePath(pathRelativeToResourcesDir) {
+ return baseResourcePath + '/' + pathRelativeToResourcesDir;
+}
+
+/**
+ * Set the base resource path (path to the `resources` directory relative to the current
+ * execution context).
+ */
+export function setBaseResourcePath(path) {
+ baseResourcePath = path;
+}
+
+/**
+ * Set the cross origin host and cases related to cross origin
+ * will load resource from the given host.
+ */
+export function setCrossOriginHost(host) {
+ crossOriginHost = host;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js
new file mode 100644
index 0000000000..81984dbec5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/test_config.js
@@ -0,0 +1,32 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+export const globalTestConfig = {
+ maxSubcasesInFlight: 500,
+ testHeartbeatCallback: () => {},
+ noRaceWithRejectOnTimeout: false,
+ unrollConstEvalLoops: false,
+ compatibility: false
+}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/framework/test_group.js b/testing/web-platform/mozilla/tests/webgpu/common/framework/test_group.js
new file mode 100644
index 0000000000..82b409c614
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/framework/test_group.js
@@ -0,0 +1,3 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export { makeTestGroup } from '../internal/test_group.js'; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js
new file mode 100644
index 0000000000..f9f4f17fb4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/file_loader.js
@@ -0,0 +1,105 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../util/util.js';
+import { parseQuery } from './query/parseQuery.js';
+
+
+import { loadTreeForQuery } from './tree.js';
+
+// A listing file, e.g. either of:
+// - `src/webgpu/listing.ts` (which is dynamically computed, has a Promise<TestSuiteListing>)
+// - `out/webgpu/listing.js` (which is pre-baked, has a TestSuiteListing)
+
+
+
+
+// A .spec.ts file, as imported.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Override the types for addEventListener/removeEventListener so the callbacks can be used as
+// strongly-typed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Base class for DefaultTestFileLoader and FakeTestFileLoader.
+
+export class TestFileLoader extends EventTarget {
+
+
+
+ async importSpecFile(suite, path) {
+ const url = `${suite}/${path.join('/')}.spec.js`;
+ this.dispatchEvent(new MessageEvent('import', { data: { url } }));
+ const ret = await this.import(url);
+ this.dispatchEvent(new MessageEvent('imported', { data: { url } }));
+ return ret;
+ }
+
+ async loadTree(
+ query,
+ {
+ subqueriesToExpand = [],
+ maxChunkTime = Infinity
+ } = {})
+ {
+ const tree = await loadTreeForQuery(this, query, {
+ subqueriesToExpand: subqueriesToExpand.map((s) => {
+ const q = parseQuery(s);
+ assert(q.level >= 2, () => `subqueriesToExpand entries should not be multi-file:\n ${q}`);
+ return q;
+ }),
+ maxChunkTime
+ });
+ this.dispatchEvent(new MessageEvent('finish'));
+ return tree;
+ }
+
+ async loadCases(query) {
+ const tree = await this.loadTree(query);
+ return tree.iterateLeaves();
+ }
+}
+
+export class DefaultTestFileLoader extends TestFileLoader {
+ async listing(suite) {
+ return (await import(`../../${suite}/listing.js`)).listing;
+ }
+
+ import(path) {
+ return import(`../../${path}`);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js
new file mode 100644
index 0000000000..234b7c2cc9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/log_message.js
@@ -0,0 +1,44 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { extractImportantStackTrace } from '../stack.js';
+export class LogMessageWithStack extends Error {
+
+
+ stackHiddenMessage = undefined;
+
+ constructor(name, ex) {
+ super(ex.message);
+
+ this.name = name;
+ this.stack = ex.stack;
+ if ('extra' in ex) {
+ this.extra = ex.extra;
+ }
+ }
+
+ /** Set a flag so the stack is not printed in toJSON(). */
+ setStackHidden(stackHiddenMessage) {
+ this.stackHiddenMessage ??= stackHiddenMessage;
+ }
+
+ toJSON() {
+ let m = this.name;
+ if (this.message) m += ': ' + this.message;
+ if (this.stack) {
+ if (this.stackHiddenMessage === undefined) {
+ m += '\n' + extractImportantStackTrace(this);
+ } else if (this.stackHiddenMessage) {
+ m += `\n at (elided: ${this.stackHiddenMessage})`;
+ }
+ }
+ return m;
+ }
+}
+
+/**
+ * Returns a string, nicely indented, for debug logs.
+ * This is used in the cmdline and wpt runtimes. In WPT, it shows up in the `*-actual.txt` file.
+ */
+export function prettyPrintLog(log) {
+ return ' - ' + log.toJSON().replace(/\n/g, '\n ');
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js
new file mode 100644
index 0000000000..224af20ddc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/logger.js
@@ -0,0 +1,30 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { version } from '../version.js';
+import { TestCaseRecorder } from './test_case_recorder.js';
+
+
+
+export class Logger {
+ static globalDebugMode = false;
+
+
+ results = new Map();
+
+ constructor({ overrideDebugMode } = {}) {
+ this.overriddenDebugMode = overrideDebugMode;
+ }
+
+ record(name) {
+ const result = { status: 'running', timems: -1 };
+ this.results.set(name, result);
+ return [
+ new TestCaseRecorder(result, this.overriddenDebugMode ?? Logger.globalDebugMode),
+ result];
+
+ }
+
+ asJSON(space) {
+ return JSON.stringify({ version, results: Array.from(this.results) }, undefined, space);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js
new file mode 100644
index 0000000000..a7eb281daf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/result.js
@@ -0,0 +1,4 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // MAINTENANCE_TODO: Add warn expectations
+export {}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js
new file mode 100644
index 0000000000..7b9a5302ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/logging/test_case_recorder.js
@@ -0,0 +1,184 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { SkipTestCase, UnexpectedPassError } from '../../framework/fixture.js';import { globalTestConfig } from '../../framework/test_config.js';import { now, assert } from '../../util/util.js';
+
+import { LogMessageWithStack } from './log_message.js';var
+
+
+LogSeverity = /*#__PURE__*/function (LogSeverity) {LogSeverity[LogSeverity["NotRun"] = 0] = "NotRun";LogSeverity[LogSeverity["Skip"] = 1] = "Skip";LogSeverity[LogSeverity["Pass"] = 2] = "Pass";LogSeverity[LogSeverity["Warn"] = 3] = "Warn";LogSeverity[LogSeverity["ExpectFailed"] = 4] = "ExpectFailed";LogSeverity[LogSeverity["ValidationFailed"] = 5] = "ValidationFailed";LogSeverity[LogSeverity["ThrewException"] = 6] = "ThrewException";return LogSeverity;}(LogSeverity || {});
+
+
+
+
+
+
+
+
+
+const kMaxLogStacks = 2;
+const kMinSeverityForStack = LogSeverity.Warn;
+
+function logSeverityToString(status) {
+ switch (status) {
+ case LogSeverity.NotRun:
+ return 'notrun';
+ case LogSeverity.Pass:
+ return 'pass';
+ case LogSeverity.Skip:
+ return 'skip';
+ case LogSeverity.Warn:
+ return 'warn';
+ default:
+ return 'fail'; // Everything else is an error
+ }
+}
+
+/** Holds onto a LiveTestCaseResult owned by the Logger, and writes the results into it. */
+export class TestCaseRecorder {
+
+ nonskippedSubcaseCount = 0;
+ inSubCase = false;
+ subCaseStatus = LogSeverity.NotRun;
+ finalCaseStatus = LogSeverity.NotRun;
+ hideStacksBelowSeverity = kMinSeverityForStack;
+ startTime = -1;
+ logs = [];
+ logLinesAtCurrentSeverity = 0;
+ debugging = false;
+ /** Used to dedup log messages which have identical stacks. */
+ messagesForPreviouslySeenStacks = new Map();
+
+ constructor(result, debugging) {
+ this.result = result;
+ this.debugging = debugging;
+ }
+
+ start() {
+ assert(this.startTime < 0, 'TestCaseRecorder cannot be reused');
+ this.startTime = now();
+ }
+
+ finish() {
+ // This is a framework error. If this assert is hit, it won't be localized
+ // to a test. The whole test run will fail out.
+ assert(this.startTime >= 0, 'internal error: finish() before start()');
+
+ const timeMilliseconds = now() - this.startTime;
+ // Round to next microsecond to avoid storing useless .xxxx00000000000002 in results.
+ this.result.timems = Math.ceil(timeMilliseconds * 1000) / 1000;
+
+ if (this.finalCaseStatus === LogSeverity.Skip && this.nonskippedSubcaseCount !== 0) {
+ this.threw(new Error('internal error: case is "skip" but has nonskipped subcases'));
+ }
+
+ // Convert numeric enum back to string (but expose 'exception' as 'fail')
+ this.result.status = logSeverityToString(this.finalCaseStatus);
+
+ this.result.logs = this.logs;
+ }
+
+ beginSubCase() {
+ this.subCaseStatus = LogSeverity.NotRun;
+ this.inSubCase = true;
+ }
+
+ endSubCase(expectedStatus) {
+ if (this.subCaseStatus !== LogSeverity.Skip) {
+ this.nonskippedSubcaseCount++;
+ }
+ try {
+ if (expectedStatus === 'fail') {
+ if (this.subCaseStatus <= LogSeverity.Warn) {
+ throw new UnexpectedPassError();
+ } else {
+ this.subCaseStatus = LogSeverity.Pass;
+ }
+ }
+ } finally {
+ this.inSubCase = false;
+ this.finalCaseStatus = Math.max(this.finalCaseStatus, this.subCaseStatus);
+ }
+ }
+
+ injectResult(injectedResult) {
+ Object.assign(this.result, injectedResult);
+ }
+
+ debug(ex) {
+ if (!this.debugging) return;
+ this.logImpl(LogSeverity.Pass, 'DEBUG', ex);
+ }
+
+ info(ex) {
+ // We need this to use the lowest LogSeverity so it doesn't override the current severity for this test case.
+ this.logImpl(LogSeverity.NotRun, 'INFO', ex);
+ }
+
+ skipped(ex) {
+ this.logImpl(LogSeverity.Skip, 'SKIP', ex);
+ }
+
+ warn(ex) {
+ this.logImpl(LogSeverity.Warn, 'WARN', ex);
+ }
+
+ expectationFailed(ex) {
+ this.logImpl(LogSeverity.ExpectFailed, 'EXPECTATION FAILED', ex);
+ }
+
+ validationFailed(ex) {
+ this.logImpl(LogSeverity.ValidationFailed, 'VALIDATION FAILED', ex);
+ }
+
+ passed() {
+ if (this.inSubCase) {
+ this.subCaseStatus = Math.max(this.subCaseStatus, LogSeverity.Pass);
+ } else {
+ this.finalCaseStatus = Math.max(this.finalCaseStatus, LogSeverity.Pass);
+ }
+ }
+
+ threw(ex) {
+ if (ex instanceof SkipTestCase) {
+ this.skipped(ex);
+ return;
+ }
+ this.logImpl(LogSeverity.ThrewException, 'EXCEPTION', ex);
+ }
+
+ logImpl(level, name, baseException) {
+ assert(baseException instanceof Error, 'test threw a non-Error object');
+ globalTestConfig.testHeartbeatCallback();
+ const logMessage = new LogMessageWithStack(name, baseException);
+
+ // Final case status should be the "worst" of all log entries.
+ if (this.inSubCase) {
+ this.subCaseStatus = Math.max(this.subCaseStatus, level);
+ } else {
+ this.finalCaseStatus = Math.max(this.finalCaseStatus, level);
+ }
+
+ // setFirstLineOnly for all logs except `kMaxLogStacks` stacks at the highest severity
+ if (level > this.hideStacksBelowSeverity) {
+ this.logLinesAtCurrentSeverity = 0;
+ this.hideStacksBelowSeverity = level;
+
+ // Go back and setFirstLineOnly for everything of a lower log level
+ for (const log of this.logs) {
+ log.setStackHidden('below max severity');
+ }
+ }
+ if (level === this.hideStacksBelowSeverity) {
+ this.logLinesAtCurrentSeverity++;
+ } else if (level < kMinSeverityForStack) {
+ logMessage.setStackHidden('');
+ } else if (level < this.hideStacksBelowSeverity) {
+ logMessage.setStackHidden('below max severity');
+ }
+ if (this.logLinesAtCurrentSeverity > kMaxLogStacks) {
+ logMessage.setStackHidden(`only ${kMaxLogStacks} shown`);
+ }
+
+ this.logs.push(logMessage);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/params_utils.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/params_utils.js
new file mode 100644
index 0000000000..68acd1b7bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/params_utils.js
@@ -0,0 +1,138 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../util/util.js';
+
+import { comparePublicParamsPaths, Ordering } from './query/compare.js';
+import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js';
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+export function paramKeyIsPublic(key) {
+ return !key.startsWith('_');
+}
+
+export function extractPublicParams(params) {
+ const publicParams = {};
+ for (const k of Object.keys(params)) {
+ if (paramKeyIsPublic(k)) {
+ publicParams[k] = params[k];
+ }
+ }
+ return publicParams;
+}
+
+/** Used to escape reserved characters in URIs */
+const kPercent = '%';
+
+export const badParamValueChars = new RegExp(
+ '[' + kParamKVSeparator + kParamSeparator + kWildcard + kPercent + ']'
+);
+
+export function publicParamsEquals(x, y) {
+ return comparePublicParamsPaths(x, y) === Ordering.Equal;
+}
+
+
+
+
+
+/**
+ * Flatten a union of interfaces into a single interface encoding the same type.
+ *
+ * Flattens a union in such a way that:
+ * `{ a: number, b?: undefined } | { b: string, a?: undefined }`
+ * (which is the value type of `[{ a: 1 }, { b: 1 }]`)
+ * becomes `{ a: number | undefined, b: string | undefined }`.
+ *
+ * And also works for `{ a: number } | { b: string }` which maps to the same.
+ */
+
+
+
+
+
+
+
+
+
+
+
+function typeAssert() {}
+{
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ typeAssert();
+ typeAssert();
+ typeAssert();
+ typeAssert();
+ typeAssert();
+
+ typeAssert();
+
+ typeAssert();
+ typeAssert();
+ typeAssert();
+ typeAssert();
+ typeAssert();
+
+ // Unexpected test results - hopefully okay to ignore these
+ typeAssert();
+ typeAssert();
+ }
+}
+
+
+
+
+
+
+/** Merges two objects into one `{ ...a, ...b }` and return it with a flattened type. */
+export function mergeParams(a, b) {
+ return { ...a, ...b };
+}
+
+/**
+ * Merges two objects into one `{ ...a, ...b }` and asserts they had no overlapping keys.
+ * This is slower than {@link mergeParams}.
+ */
+export function mergeParamsChecked(a, b) {
+ const merged = mergeParams(a, b);
+ assert(
+ Object.keys(merged).length === Object.keys(a).length + Object.keys(b).length,
+ () => `Duplicate key between ${JSON.stringify(a)} and ${JSON.stringify(b)}`
+ );
+ return merged;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js
new file mode 100644
index 0000000000..8af64919a2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/compare.js
@@ -0,0 +1,95 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, objectEquals } from '../../util/util.js';import { paramKeyIsPublic } from '../params_utils.js';
+
+
+
+export let Ordering = /*#__PURE__*/function (Ordering) {Ordering[Ordering["Unordered"] = 0] = "Unordered";Ordering[Ordering["StrictSuperset"] = 1] = "StrictSuperset";Ordering[Ordering["Equal"] = 2] = "Equal";Ordering[Ordering["StrictSubset"] = 3] = "StrictSubset";return Ordering;}({});
+
+
+
+
+
+
+/**
+ * Compares two queries for their ordering (which is used to build the tree).
+ *
+ * See src/unittests/query_compare.spec.ts for examples.
+ */
+export function compareQueries(a, b) {
+ if (a.suite !== b.suite) {
+ return Ordering.Unordered;
+ }
+
+ const filePathOrdering = comparePaths(a.filePathParts, b.filePathParts);
+ if (filePathOrdering !== Ordering.Equal || a.isMultiFile || b.isMultiFile) {
+ return compareOneLevel(filePathOrdering, a.isMultiFile, b.isMultiFile);
+ }
+ assert('testPathParts' in a && 'testPathParts' in b);
+
+ const testPathOrdering = comparePaths(a.testPathParts, b.testPathParts);
+ if (testPathOrdering !== Ordering.Equal || a.isMultiTest || b.isMultiTest) {
+ return compareOneLevel(testPathOrdering, a.isMultiTest, b.isMultiTest);
+ }
+ assert('params' in a && 'params' in b);
+
+ const paramsPathOrdering = comparePublicParamsPaths(a.params, b.params);
+ if (paramsPathOrdering !== Ordering.Equal || a.isMultiCase || b.isMultiCase) {
+ return compareOneLevel(paramsPathOrdering, a.isMultiCase, b.isMultiCase);
+ }
+ return Ordering.Equal;
+}
+
+/**
+ * Compares a single level of a query.
+ *
+ * "IsBig" means the query is big relative to the level, e.g. for test-level:
+ * - Anything >= `suite:a,*` is big
+ * - Anything <= `suite:a:*` is small
+ */
+function compareOneLevel(ordering, aIsBig, bIsBig) {
+ assert(ordering !== Ordering.Equal || aIsBig || bIsBig);
+ if (ordering === Ordering.Unordered) return Ordering.Unordered;
+ if (aIsBig && bIsBig) return ordering;
+ if (!aIsBig && !bIsBig) return Ordering.Unordered; // Equal case is already handled
+ // Exactly one of (a, b) is big.
+ if (aIsBig && ordering !== Ordering.StrictSubset) return Ordering.StrictSuperset;
+ if (bIsBig && ordering !== Ordering.StrictSuperset) return Ordering.StrictSubset;
+ return Ordering.Unordered;
+}
+
+function comparePaths(a, b) {
+ const shorter = Math.min(a.length, b.length);
+
+ for (let i = 0; i < shorter; ++i) {
+ if (a[i] !== b[i]) {
+ return Ordering.Unordered;
+ }
+ }
+ if (a.length === b.length) {
+ return Ordering.Equal;
+ } else if (a.length < b.length) {
+ return Ordering.StrictSuperset;
+ } else {
+ return Ordering.StrictSubset;
+ }
+}
+
+export function comparePublicParamsPaths(a, b) {
+ const aKeys = Object.keys(a).filter((k) => paramKeyIsPublic(k));
+ const commonKeys = new Set(aKeys.filter((k) => k in b));
+
+ for (const k of commonKeys) {
+ // Treat +/-0.0 as different query by distinguishing them in objectEquals
+ if (!objectEquals(a[k], b[k], true)) {
+ return Ordering.Unordered;
+ }
+ }
+ const bKeys = Object.keys(b).filter((k) => paramKeyIsPublic(k));
+ const aRemainingKeys = aKeys.length - commonKeys.size;
+ const bRemainingKeys = bKeys.length - commonKeys.size;
+ if (aRemainingKeys === 0 && bRemainingKeys === 0) return Ordering.Equal;
+ if (aRemainingKeys === 0) return Ordering.StrictSuperset;
+ if (bRemainingKeys === 0) return Ordering.StrictSubset;
+ return Ordering.Unordered;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/encode_selectively.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/encode_selectively.js
new file mode 100644
index 0000000000..fe7bb1c1a5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/encode_selectively.js
@@ -0,0 +1,23 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Encodes a stringified TestQuery so that it can be placed in a `?q=` parameter in a URL.
+ *
+ * `encodeURIComponent` encodes in accordance with `application/x-www-form-urlencoded`,
+ * but URLs don't actually have to be as strict as HTML form encoding
+ * (we interpret this purely from JavaScript).
+ * So we encode the component, then selectively convert some %-encoded escape codes
+ * back to their original form for readability/copyability.
+ */export function encodeURIComponentSelectively(s) {let ret = encodeURIComponent(s);
+ ret = ret.replace(/%22/g, '"'); // for JSON strings
+ ret = ret.replace(/%2C/g, ','); // for path separator, and JSON arrays
+ ret = ret.replace(/%3A/g, ':'); // for big separator
+ ret = ret.replace(/%3B/g, ';'); // for param separator
+ ret = ret.replace(/%3D/g, '='); // for params (k=v)
+ ret = ret.replace(/%5B/g, '['); // for JSON arrays
+ ret = ret.replace(/%5D/g, ']'); // for JSON arrays
+ ret = ret.replace(/%7B/g, '{'); // for JSON objects
+ ret = ret.replace(/%7D/g, '}'); // for JSON objects
+ ret = ret.replace(/%E2%9C%97/g, '✗'); // for jsUndefinedMagicValue
+ return ret;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/json_param_value.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/json_param_value.js
new file mode 100644
index 0000000000..ce7be0335f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/json_param_value.js
@@ -0,0 +1,114 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, sortObjectByKey, isPlainObject } from '../../util/util.js';
+// JSON can't represent various values and by default stores them as `null`.
+// Instead, storing them as a magic string values in JSON.
+const jsUndefinedMagicValue = '_undef_';
+const jsNaNMagicValue = '_nan_';
+const jsPositiveInfinityMagicValue = '_posinfinity_';
+const jsNegativeInfinityMagicValue = '_neginfinity_';
+
+// -0 needs to be handled separately, because -0 === +0 returns true. Not
+// special casing +0/0, since it behaves intuitively. Assuming that if -0 is
+// being used, the differentiation from +0 is desired.
+const jsNegativeZeroMagicValue = '_negzero_';
+
+// bigint values are not defined in JSON, so need to wrap them up as strings
+const jsBigIntMagicPattern = /^(\d+)n$/;
+
+const toStringMagicValue = new Map([
+[undefined, jsUndefinedMagicValue],
+[NaN, jsNaNMagicValue],
+[Number.POSITIVE_INFINITY, jsPositiveInfinityMagicValue],
+[Number.NEGATIVE_INFINITY, jsNegativeInfinityMagicValue]
+// No -0 handling because it is special cased.
+]);
+
+const fromStringMagicValue = new Map([
+[jsUndefinedMagicValue, undefined],
+[jsNaNMagicValue, NaN],
+[jsPositiveInfinityMagicValue, Number.POSITIVE_INFINITY],
+[jsNegativeInfinityMagicValue, Number.NEGATIVE_INFINITY],
+// -0 is handled in this direction because there is no comparison issue.
+[jsNegativeZeroMagicValue, -0]]
+);
+
+function stringifyFilter(_k, v) {
+ // Make sure no one actually uses a magic value as a parameter.
+ if (typeof v === 'string') {
+ assert(
+ !fromStringMagicValue.has(v),
+ `${v} is a magic value for stringification, so cannot be used`
+ );
+
+ assert(
+ v !== jsNegativeZeroMagicValue,
+ `${v} is a magic value for stringification, so cannot be used`
+ );
+
+ assert(
+ v.match(jsBigIntMagicPattern) === null,
+ `${v} matches bigint magic pattern for stringification, so cannot be used`
+ );
+ }
+
+ const isObject = v !== null && typeof v === 'object' && !Array.isArray(v);
+ if (isObject) {
+ assert(
+ isPlainObject(v),
+ `value must be a plain object but it appears to be a '${
+ Object.getPrototypeOf(v).constructor.name
+ }`
+ );
+ }
+ assert(typeof v !== 'function', `${v} can not be a function`);
+
+ if (Object.is(v, -0)) {
+ return jsNegativeZeroMagicValue;
+ }
+
+ if (typeof v === 'bigint') {
+ return `${v}n`;
+ }
+
+ return toStringMagicValue.has(v) ? toStringMagicValue.get(v) : v;
+}
+
+export function stringifyParamValue(value) {
+ return JSON.stringify(value, stringifyFilter);
+}
+
+/**
+ * Like stringifyParamValue but sorts dictionaries by key, for hashing.
+ */
+export function stringifyParamValueUniquely(value) {
+ return JSON.stringify(value, (k, v) => {
+ if (typeof v === 'object' && v !== null) {
+ return sortObjectByKey(v);
+ }
+
+ return stringifyFilter(k, v);
+ });
+}
+
+// 'any' is part of the JSON.parse reviver interface, so cannot be avoided.
+
+function parseParamValueReviver(_k, v) {
+ if (fromStringMagicValue.has(v)) {
+ return fromStringMagicValue.get(v);
+ }
+
+ if (typeof v === 'string') {
+ const match = v.match(jsBigIntMagicPattern);
+ if (match !== null) {
+ // [0] is the entire match, and following entries are the capture groups
+ return BigInt(match[1]);
+ }
+ }
+
+ return v;
+}
+
+export function parseParamValue(s) {
+ return JSON.parse(s, parseParamValueReviver);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js
new file mode 100644
index 0000000000..b66b16ce91
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/parseQuery.js
@@ -0,0 +1,155 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../util/util.js';import {
+
+ badParamValueChars,
+ paramKeyIsPublic } from
+'../params_utils.js';
+
+import { parseParamValue } from './json_param_value.js';
+import {
+
+ TestQueryMultiFile,
+ TestQueryMultiTest,
+ TestQueryMultiCase,
+ TestQuerySingleCase } from
+'./query.js';
+import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js';
+import { validQueryPart } from './validQueryPart.js';
+
+export function parseQuery(s) {
+ try {
+ return parseQueryImpl(s);
+ } catch (ex) {
+ if (ex instanceof Error) {
+ ex.message += '\n on: ' + s;
+ }
+ throw ex;
+ }
+}
+
+function parseQueryImpl(s) {
+ // Undo encodeURIComponentSelectively
+ s = decodeURIComponent(s);
+
+ // bigParts are: suite, file, test, params (note kBigSeparator could appear in params)
+ let suite;
+ let fileString;
+ let testString;
+ let paramsString;
+ {
+ const i1 = s.indexOf(kBigSeparator);
+ assert(i1 !== -1, `query string must have at least one ${kBigSeparator}`);
+ suite = s.substring(0, i1);
+ const i2 = s.indexOf(kBigSeparator, i1 + 1);
+ if (i2 === -1) {
+ fileString = s.substring(i1 + 1);
+ } else {
+ fileString = s.substring(i1 + 1, i2);
+ const i3 = s.indexOf(kBigSeparator, i2 + 1);
+ if (i3 === -1) {
+ testString = s.substring(i2 + 1);
+ } else {
+ testString = s.substring(i2 + 1, i3);
+ paramsString = s.substring(i3 + 1);
+ }
+ }
+ }
+
+ const { parts: file, wildcard: filePathHasWildcard } = parseBigPart(fileString, kPathSeparator);
+
+ if (testString === undefined) {
+ // Query is file-level
+ assert(
+ filePathHasWildcard,
+ `File-level query without wildcard ${kWildcard}. Did you want a file-level query \
+(append ${kPathSeparator}${kWildcard}) or test-level query (append ${kBigSeparator}${kWildcard})?`
+ );
+ return new TestQueryMultiFile(suite, file);
+ }
+ assert(!filePathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
+
+ const { parts: test, wildcard: testPathHasWildcard } = parseBigPart(testString, kPathSeparator);
+
+ if (paramsString === undefined) {
+ // Query is test-level
+ assert(
+ testPathHasWildcard,
+ `Test-level query without wildcard ${kWildcard}; did you want a test-level query \
+(append ${kPathSeparator}${kWildcard}) or case-level query (append ${kBigSeparator}${kWildcard})?`
+ );
+ assert(file.length > 0, 'File part of test-level query was empty (::)');
+ return new TestQueryMultiTest(suite, file, test);
+ }
+
+ // Query is case-level
+ assert(!testPathHasWildcard, `Wildcard ${kWildcard} must be at the end of the query string`);
+
+ const { parts: paramsParts, wildcard: paramsHasWildcard } = parseBigPart(
+ paramsString,
+ kParamSeparator
+ );
+
+ assert(test.length > 0, 'Test part of case-level query was empty (::)');
+
+ const params = {};
+ for (const paramPart of paramsParts) {
+ const [k, v] = parseSingleParam(paramPart);
+ assert(validQueryPart.test(k), `param key names must match ${validQueryPart}`);
+ params[k] = v;
+ }
+ if (paramsHasWildcard) {
+ return new TestQueryMultiCase(suite, file, test, params);
+ } else {
+ return new TestQuerySingleCase(suite, file, test, params);
+ }
+}
+
+// webgpu:a,b,* or webgpu:a,b,c:*
+const kExampleQueries = `\
+webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}${kWildcard} or \
+webgpu${kBigSeparator}a${kPathSeparator}b${kPathSeparator}c${kBigSeparator}${kWildcard}`;
+
+function parseBigPart(
+s,
+separator)
+{
+ if (s === '') {
+ return { parts: [], wildcard: false };
+ }
+ const parts = s.split(separator);
+
+ let endsWithWildcard = false;
+ for (const [i, part] of parts.entries()) {
+ if (i === parts.length - 1) {
+ endsWithWildcard = part === kWildcard;
+ }
+ assert(
+ part.indexOf(kWildcard) === -1 || endsWithWildcard,
+ `Wildcard ${kWildcard} must be complete last part of a path (e.g. ${kExampleQueries})`
+ );
+ }
+ if (endsWithWildcard) {
+ // Remove the last element of the array (which is just the wildcard).
+ parts.length = parts.length - 1;
+ }
+ return { parts, wildcard: endsWithWildcard };
+}
+
+function parseSingleParam(paramSubstring) {
+ assert(paramSubstring !== '', 'Param in a query must not be blank (is there a trailing comma?)');
+ const i = paramSubstring.indexOf('=');
+ assert(i !== -1, 'Param in a query must be of form key=value');
+ const k = paramSubstring.substring(0, i);
+ assert(paramKeyIsPublic(k), 'Param in a query must not be private (start with _)');
+ const v = paramSubstring.substring(i + 1);
+ return [k, parseSingleParamValue(v)];
+}
+
+function parseSingleParamValue(s) {
+ assert(
+ !badParamValueChars.test(s),
+ `param value must not match ${badParamValueChars} - was ${s}`
+ );
+ return parseParamValue(s);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js
new file mode 100644
index 0000000000..e1db875061
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/query.js
@@ -0,0 +1,262 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { optionEnabled } from '../../runtime/helper/options.js';import { assert, unreachable } from '../../util/util.js';
+
+
+import { compareQueries, Ordering } from './compare.js';
+import { encodeURIComponentSelectively } from './encode_selectively.js';
+import { parseQuery } from './parseQuery.js';
+import { kBigSeparator, kPathSeparator, kWildcard } from './separators.js';
+import { stringifyPublicParams } from './stringify_params.js';
+
+/**
+ * Represents a test query of some level.
+ *
+ * TestQuery types are immutable.
+ */
+
+
+
+
+
+
+/**
+ * - 1 = MultiFile.
+ * - 2 = MultiTest.
+ * - 3 = MultiCase.
+ * - 4 = SingleCase.
+ */
+
+
+
+
+
+
+
+/**
+ * A multi-file test query, like `s:*` or `s:a,b,*`.
+ *
+ * Immutable (makes copies of constructor args).
+ */
+export class TestQueryMultiFile {
+ level = 1;
+ isMultiFile = true;
+
+
+
+ constructor(suite, file) {
+ this.suite = suite;
+ this.filePathParts = [...file];
+ }
+
+ get depthInLevel() {
+ return this.filePathParts.length;
+ }
+
+ toString() {
+ return encodeURIComponentSelectively(this.toStringHelper().join(kBigSeparator));
+ }
+
+ toStringHelper() {
+ return [this.suite, [...this.filePathParts, kWildcard].join(kPathSeparator)];
+ }
+}
+
+/**
+ * A multi-test test query, like `s:f:*` or `s:f:a,b,*`.
+ *
+ * Immutable (makes copies of constructor args).
+ */
+export class TestQueryMultiTest extends TestQueryMultiFile {
+ level = 2;
+ isMultiFile = false;
+ isMultiTest = true;
+
+
+ constructor(suite, file, test) {
+ super(suite, file);
+ assert(file.length > 0, 'multi-test (or finer) query must have file-path');
+ this.testPathParts = [...test];
+ }
+
+ get depthInLevel() {
+ return this.testPathParts.length;
+ }
+
+ toStringHelper() {
+ return [
+ this.suite,
+ this.filePathParts.join(kPathSeparator),
+ [...this.testPathParts, kWildcard].join(kPathSeparator)];
+
+ }
+}
+
+/**
+ * A multi-case test query, like `s:f:t:*` or `s:f:t:a,b,*`.
+ *
+ * Immutable (makes copies of constructor args), except for param values
+ * (which aren't normally supposed to change; they're marked readonly in TestParams).
+ */
+export class TestQueryMultiCase extends TestQueryMultiTest {
+ level = 3;
+ isMultiTest = false;
+ isMultiCase = true;
+
+
+ constructor(suite, file, test, params) {
+ super(suite, file, test);
+ assert(test.length > 0, 'multi-case (or finer) query must have test-path');
+ this.params = { ...params };
+ }
+
+ get depthInLevel() {
+ return Object.keys(this.params).length;
+ }
+
+ toStringHelper() {
+ return [
+ this.suite,
+ this.filePathParts.join(kPathSeparator),
+ this.testPathParts.join(kPathSeparator),
+ stringifyPublicParams(this.params, true)];
+
+ }
+}
+
+/**
+ * A multi-case test query, like `s:f:t:` or `s:f:t:a=1,b=1`.
+ *
+ * Immutable (makes copies of constructor args).
+ */
+export class TestQuerySingleCase extends TestQueryMultiCase {
+ level = 4;
+ isMultiCase = false;
+
+ get depthInLevel() {
+ return 0;
+ }
+
+ toStringHelper() {
+ return [
+ this.suite,
+ this.filePathParts.join(kPathSeparator),
+ this.testPathParts.join(kPathSeparator),
+ stringifyPublicParams(this.params)];
+
+ }
+}
+
+/**
+ * Parse raw expectations input into TestQueryWithExpectation[], filtering so that only
+ * expectations that are relevant for the provided query and wptURL.
+ *
+ * `rawExpectations` should be @type {{ query: string, expectation: Expectation }[]}
+ *
+ * The `rawExpectations` are parsed and validated that they are in the correct format.
+ * If `wptURL` is passed, the query string should be of the full path format such
+ * as `path/to/cts.https.html?worker=0&q=suite:test_path:test_name:foo=1;bar=2;*`.
+ * If `wptURL` is `undefined`, the query string should be only the query
+ * `suite:test_path:test_name:foo=1;bar=2;*`.
+ */
+export function parseExpectationsForTestQuery(
+rawExpectations,
+
+
+
+
+
+query,
+wptURL)
+{
+ if (!Array.isArray(rawExpectations)) {
+ unreachable('Expectations should be an array');
+ }
+ const expectations = [];
+ for (const entry of rawExpectations) {
+ assert(typeof entry === 'object');
+ const rawExpectation = entry;
+ assert(rawExpectation.query !== undefined, 'Expectation missing query string');
+ assert(rawExpectation.expectation !== undefined, 'Expectation missing expectation string');
+
+ let expectationQuery;
+ if (wptURL !== undefined) {
+ const expectationURL = new URL(`${wptURL.origin}/${entry.query}`);
+ if (expectationURL.pathname !== wptURL.pathname) {
+ continue;
+ }
+ 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;...
+ `
+ );
+
+ const params = expectationURL.searchParams;
+ if (optionEnabled('worker', params) !== optionEnabled('worker', wptURL.searchParams)) {
+ continue;
+ }
+
+ const qs = params.getAll('q');
+ assert(qs.length === 1, 'currently, there must be exactly one ?q= in the expectation string');
+ expectationQuery = parseQuery(qs[0]);
+ } else {
+ expectationQuery = parseQuery(entry.query);
+ }
+
+ // Strip params from multicase expectations so that an expectation of foo=2;*
+ // is stored if the test query is bar=3;*
+ const queryForFilter =
+ expectationQuery instanceof TestQueryMultiCase ?
+ new TestQueryMultiCase(
+ expectationQuery.suite,
+ expectationQuery.filePathParts,
+ expectationQuery.testPathParts,
+ {}
+ ) :
+ expectationQuery;
+
+ if (compareQueries(query, queryForFilter) === Ordering.Unordered) {
+ continue;
+ }
+
+ switch (entry.expectation) {
+ case 'pass':
+ case 'skip':
+ case 'fail':
+ break;
+ default:
+ unreachable(`Invalid expectation ${entry.expectation}`);
+ }
+
+ expectations.push({
+ query: expectationQuery,
+ expectation: entry.expectation
+ });
+ }
+ return expectations;
+}
+
+/**
+ * For display purposes only, produces a "relative" query string from parent to child.
+ * Used in the wpt runtime to reduce the verbosity of logs.
+ */
+export function relativeQueryString(parent, child) {
+ const ordering = compareQueries(parent, child);
+ if (ordering === Ordering.Equal) {
+ return '';
+ } else if (ordering === Ordering.StrictSuperset) {
+ const parentString = parent.toString();
+ assert(parentString.endsWith(kWildcard));
+ const childString = child.toString();
+ assert(
+ childString.startsWith(parentString.substring(0, parentString.length - 2)),
+ 'impossible?: childString does not start with parentString[:-2]'
+ );
+ return childString.substring(parentString.length - 2);
+ } else {
+ unreachable(
+ `relativeQueryString arguments have invalid ordering ${ordering}:\n${parent}\n${child}`
+ );
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/separators.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/separators.js
new file mode 100644
index 0000000000..6170cb5002
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/separators.js
@@ -0,0 +1,14 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /** Separator between big parts: suite:file:test:case */export const kBigSeparator = ':';
+/** Separator between path,to,file or path,to,test */
+export const kPathSeparator = ',';
+
+/** Separator between k=v;k=v */
+export const kParamSeparator = ';';
+
+/** Separator between key and value in k=v */
+export const kParamKVSeparator = '=';
+
+/** Final wildcard, if query is not single-case */
+export const kWildcard = '*'; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/stringify_params.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/stringify_params.js
new file mode 100644
index 0000000000..74808131a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/stringify_params.js
@@ -0,0 +1,44 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../util/util.js';import { badParamValueChars, paramKeyIsPublic } from '../params_utils.js';
+
+import { stringifyParamValue, stringifyParamValueUniquely } from './json_param_value.js';
+import { kParamKVSeparator, kParamSeparator, kWildcard } from './separators.js';
+
+export function stringifyPublicParams(p, addWildcard = false) {
+ const parts = Object.keys(p).
+ filter((k) => paramKeyIsPublic(k)).
+ map((k) => stringifySingleParam(k, p[k]));
+
+ if (addWildcard) parts.push(kWildcard);
+
+ return parts.join(kParamSeparator);
+}
+
+/**
+ * An _approximately_ unique string representing a CaseParams value.
+ */
+export function stringifyPublicParamsUniquely(p) {
+ const keys = Object.keys(p).sort();
+ return keys.
+ filter((k) => paramKeyIsPublic(k)).
+ map((k) => stringifySingleParamUniquely(k, p[k])).
+ join(kParamSeparator);
+}
+
+export function stringifySingleParam(k, v) {
+ return `${k}${kParamKVSeparator}${stringifySingleParamValue(v)}`;
+}
+
+function stringifySingleParamUniquely(k, v) {
+ return `${k}${kParamKVSeparator}${stringifyParamValueUniquely(v)}`;
+}
+
+function stringifySingleParamValue(v) {
+ const s = stringifyParamValue(v);
+ assert(
+ !badParamValueChars.test(s),
+ `JSON.stringified param value must not match ${badParamValueChars} - was ${s}`
+ );
+ return s;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/query/validQueryPart.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/validQueryPart.js
new file mode 100644
index 0000000000..37e5d08073
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/query/validQueryPart.js
@@ -0,0 +1,3 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /** Applies to group parts, test parts, params keys. */export const validQueryPart = /^[a-zA-Z0-9_]+$/; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/stack.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/stack.js
new file mode 100644
index 0000000000..8db1ff940d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/stack.js
@@ -0,0 +1,82 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // Returns the stack trace of an Error, but without the extra boilerplate at the bottom
+// (e.g. RunCaseSpecific, processTicksAndRejections, etc.), for logging.
+export function extractImportantStackTrace(e) {let stack = e.stack;if (!stack) {
+ return '';
+ }
+ const redundantMessage = 'Error: ' + e.message + '\n';
+ if (stack.startsWith(redundantMessage)) {
+ stack = stack.substring(redundantMessage.length);
+ }
+
+ const lines = stack.split('\n');
+ for (let i = lines.length - 1; i >= 0; --i) {
+ const line = lines[i];
+ if (line.indexOf('.spec.') !== -1) {
+ return lines.slice(0, i + 1).join('\n');
+ }
+ }
+ return stack;
+}
+
+// *** Examples ***
+//
+// Node fail()
+// > Error:
+// > at CaseRecorder.fail (/Users/kainino/src/cts/src/common/framework/logger.ts:99:30)
+// > at RunCaseSpecific.exports.g.test.t [as fn] (/Users/kainino/src/cts/src/unittests/logger.spec.ts:80:7)
+// x at RunCaseSpecific.run (/Users/kainino/src/cts/src/common/framework/test_group.ts:121:18)
+// x at processTicksAndRejections (internal/process/task_queues.js:86:5)
+//
+// Node throw
+// > Error: hello
+// > at RunCaseSpecific.g.test.t [as fn] (/Users/kainino/src/cts/src/unittests/test_group.spec.ts:51:11)
+// x at RunCaseSpecific.run (/Users/kainino/src/cts/src/common/framework/test_group.ts:121:18)
+// x at processTicksAndRejections (internal/process/task_queues.js:86:5)
+//
+// Firefox fail()
+// > fail@http://localhost:8080/out/framework/logger.js:104:30
+// > expect@http://localhost:8080/out/framework/default_fixture.js:59:16
+// > @http://localhost:8080/out/unittests/util.spec.js:35:5
+// x run@http://localhost:8080/out/framework/test_group.js:119:18
+//
+// Firefox throw
+// > @http://localhost:8080/out/unittests/test_group.spec.js:48:11
+// x run@http://localhost:8080/out/framework/test_group.js:119:18
+//
+// Safari fail()
+// > fail@http://localhost:8080/out/framework/logger.js:104:39
+// > expect@http://localhost:8080/out/framework/default_fixture.js:59:20
+// > http://localhost:8080/out/unittests/util.spec.js:35:11
+// x http://localhost:8080/out/framework/test_group.js:119:20
+// x asyncFunctionResume@[native code]
+// x [native code]
+// x promiseReactionJob@[native code]
+//
+// Safari throw
+// > http://localhost:8080/out/unittests/test_group.spec.js:48:20
+// x http://localhost:8080/out/framework/test_group.js:119:20
+// x asyncFunctionResume@[native code]
+// x [native code]
+// x promiseReactionJob@[native code]
+//
+// Chrome fail()
+// x Error
+// x at CaseRecorder.fail (http://localhost:8080/out/framework/logger.js:104:30)
+// x at DefaultFixture.expect (http://localhost:8080/out/framework/default_fixture.js:59:16)
+// > at RunCaseSpecific.fn (http://localhost:8080/out/unittests/util.spec.js:35:5)
+// x at RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:18)
+// x at async runCase (http://localhost:8080/out/runtime/standalone.js:37:17)
+// x at async http://localhost:8080/out/runtime/standalone.js:102:7
+//
+// Chrome throw
+// x Error: hello
+// > at RunCaseSpecific.fn (http://localhost:8080/out/unittests/test_group.spec.js:48:11)
+// x at RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:18)"
+// x at async Promise.all (index 0)
+// x at async TestGroupTest.run (http://localhost:8080/out/unittests/test_group_test.js:6:5)
+// x at async RunCaseSpecific.fn (http://localhost:8080/out/unittests/test_group.spec.js:53:15)
+// x at async RunCaseSpecific.run (http://localhost:8080/out/framework/test_group.js:119:7)
+// x at async runCase (http://localhost:8080/out/runtime/standalone.js:37:17)
+// x at async http://localhost:8080/out/runtime/standalone.js:102:7 \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js
new file mode 100644
index 0000000000..3db409ffe7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_group.js
@@ -0,0 +1,743 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import {
+ SkipTestCase,
+
+ UnexpectedPassError } from
+
+
+'../framework/fixture.js';
+import {
+
+ builderIterateCasesWithSubcases,
+ kUnitCaseParamsBuilder } from
+
+
+'../framework/params_builder.js';
+import { globalTestConfig } from '../framework/test_config.js';
+
+import { TestCaseRecorder } from '../internal/logging/test_case_recorder.js';
+import { extractPublicParams, mergeParams } from '../internal/params_utils.js';
+import { compareQueries, Ordering } from '../internal/query/compare.js';
+import {
+
+ TestQueryMultiTest,
+ TestQuerySingleCase } from
+
+'../internal/query/query.js';
+import { kPathSeparator } from '../internal/query/separators.js';
+import {
+ stringifyPublicParams,
+ stringifyPublicParamsUniquely } from
+'../internal/query/stringify_params.js';
+import { validQueryPart } from '../internal/query/validQueryPart.js';
+
+import { assert, unreachable } from '../util/util.js';
+
+import { logToWebsocket } from './websocket_logger.js';
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Interface for defining tests
+
+
+
+export function makeTestGroup(fixture) {
+ return new TestGroup(fixture);
+}
+
+// Interfaces for running tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+export function makeTestGroupForUnitTesting(
+fixture)
+{
+ return new TestGroup(fixture);
+}
+
+/** The maximum allowed length of a test query string. Checked by tools/validate. */
+export const kQueryMaxLength = 375;
+
+/** Parameter name for batch number (see also TestBuilder.batch). */
+const kBatchParamName = 'batch__';
+
+
+
+
+
+
+
+
+export class TestGroup {
+
+ seen = new Set();
+ tests = [];
+
+ constructor(fixture) {
+ this.fixture = fixture;
+ }
+
+ iterate() {
+ return this.tests;
+ }
+
+ checkName(name) {
+ assert(
+ // Shouldn't happen due to the rule above. Just makes sure that treating
+ // unencoded strings as encoded strings is OK.
+ name === decodeURIComponent(name),
+ `Not decodeURIComponent-idempotent: ${name} !== ${decodeURIComponent(name)}`
+ );
+ assert(!this.seen.has(name), `Duplicate test name: ${name}`);
+
+ this.seen.add(name);
+ }
+
+ test(name) {
+ const testCreationStack = new Error(`Test created: ${name}`);
+
+ this.checkName(name);
+
+ const parts = name.split(kPathSeparator);
+ for (const p of parts) {
+ assert(validQueryPart.test(p), `Invalid test name part ${p}; must match ${validQueryPart}`);
+ }
+
+ const test = new TestBuilder(parts, this.fixture, testCreationStack);
+ this.tests.push(test);
+ return test;
+ }
+
+ validate(fileQuery) {
+ for (const test of this.tests) {
+ const testQuery = new TestQueryMultiTest(
+ fileQuery.suite,
+ fileQuery.filePathParts,
+ test.testPath
+ );
+ test.validate(testQuery);
+ }
+ }
+
+ collectNonEmptyTests() {
+ const testPaths = [];
+ for (const test of this.tests) {
+ if (test.computeCaseCount() > 0) {
+ testPaths.push({ testPath: test.testPath });
+ }
+ }
+ return testPaths;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+class TestBuilder {
+
+
+
+
+
+
+
+
+ testCases = undefined;
+ batchSize = 0;
+
+ constructor(testPath, fixture, testCreationStack) {
+ this.testPath = testPath;
+ this.isUnimplemented = false;
+ this.fixture = fixture;
+ this.testCreationStack = testCreationStack;
+ }
+
+ desc(description) {
+ this.description = description.trim();
+ return this;
+ }
+
+ specURL(_url) {
+ return this;
+ }
+
+ beforeAllSubcases(fn) {
+ assert(this.beforeFn === undefined);
+ this.beforeFn = fn;
+ return this;
+ }
+
+ fn(fn) {
+
+ // MAINTENANCE_TODO: add "TODO" if there's no description? (and make sure it only ends up on
+ // actual tests, not on test parents in the tree, which is what happens if you do it here, not
+ // sure why)
+ assert(this.testFn === undefined);
+ this.testFn = fn;
+ }
+
+ batch(b) {
+ this.batchSize = b;
+ return this;
+ }
+
+ unimplemented() {
+ assert(this.testFn === undefined);
+
+ this.description =
+ (this.description ? this.description + '\n\n' : '') + 'TODO: .unimplemented()';
+ this.isUnimplemented = true;
+
+ this.testFn = () => {
+ throw new SkipTestCase('test unimplemented');
+ };
+ }
+
+ /** Perform various validation/"lint" chenks. */
+ validate(testQuery) {
+ const testPathString = this.testPath.join(kPathSeparator);
+ assert(this.testFn !== undefined, () => {
+ let s = `Test is missing .fn(): ${testPathString}`;
+ if (this.testCreationStack.stack) {
+ s += `\n-> test created at:\n${this.testCreationStack.stack}`;
+ }
+ return s;
+ });
+
+ assert(
+ testQuery.toString().length <= kQueryMaxLength,
+ () =>
+ `Test query ${testQuery} is too long. Max length is ${kQueryMaxLength} characters. Please shorten names or reduce parameters.`
+ );
+
+ if (this.testCases === undefined) {
+ return;
+ }
+
+ const seen = new Set();
+ for (const [caseParams, subcases] of builderIterateCasesWithSubcases(this.testCases, null)) {
+ const caseQuery = new TestQuerySingleCase(
+ testQuery.suite,
+ testQuery.filePathParts,
+ testQuery.testPathParts,
+ caseParams
+ ).toString();
+ assert(
+ caseQuery.length <= kQueryMaxLength,
+ () =>
+ `Case query ${caseQuery} is too long. Max length is ${kQueryMaxLength} characters. Please shorten names or reduce parameters.`
+ );
+
+ for (const subcaseParams of subcases ?? [{}]) {
+ const params = mergeParams(caseParams, subcaseParams);
+ assert(this.batchSize === 0 || !(kBatchParamName in params));
+
+ // stringifyPublicParams also checks for invalid params values
+ let testcaseString;
+ try {
+ testcaseString = stringifyPublicParams(params);
+ } catch (e) {
+ throw new Error(`${e}: ${testPathString}`);
+ }
+
+ // A (hopefully) unique representation of a params value.
+ const testcaseStringUnique = stringifyPublicParamsUniquely(params);
+ assert(
+ !seen.has(testcaseStringUnique),
+ `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString}`
+ );
+ seen.add(testcaseStringUnique);
+ }
+ }
+ }
+
+ computeCaseCount() {
+ if (this.testCases === undefined) {
+ return 1;
+ }
+
+ let caseCount = 0;
+ for (const [_caseParams, _subcases] of builderIterateCasesWithSubcases(this.testCases, null)) {
+ caseCount++;
+ }
+ return caseCount;
+ }
+
+ params(
+ cases)
+ {
+ assert(this.testCases === undefined, 'test case is already parameterized');
+ if (cases instanceof Function) {
+ this.testCases = cases(kUnitCaseParamsBuilder);
+ } else {
+ this.testCases = cases;
+ }
+ return this;
+ }
+
+ paramsSimple(cases) {
+ assert(this.testCases === undefined, 'test case is already parameterized');
+ this.testCases = kUnitCaseParamsBuilder.combineWithParams(cases);
+ return this;
+ }
+
+ paramsSubcasesOnly(
+ subcases)
+ {
+ if (subcases instanceof Function) {
+ return this.params(subcases(kUnitCaseParamsBuilder.beginSubcases()));
+ } else {
+ return this.params(kUnitCaseParamsBuilder.beginSubcases().combineWithParams(subcases));
+ }
+ }
+
+ makeCaseSpecific(params, subcases) {
+ assert(this.testFn !== undefined, 'No test function (.fn()) for test');
+ return new RunCaseSpecific(
+ this.testPath,
+ params,
+ this.isUnimplemented,
+ subcases,
+ this.fixture,
+ this.testFn,
+ this.beforeFn,
+ this.testCreationStack
+ );
+ }
+
+ *iterate(caseFilter) {
+ this.testCases ??= kUnitCaseParamsBuilder;
+
+ // Remove the batch__ from the caseFilter because the params builder doesn't
+ // know about it (we don't add it until later in this function).
+ let filterToBatch;
+ const caseFilterWithoutBatch = caseFilter ? { ...caseFilter } : null;
+ if (caseFilterWithoutBatch && kBatchParamName in caseFilterWithoutBatch) {
+ const batchParam = caseFilterWithoutBatch[kBatchParamName];
+ assert(typeof batchParam === 'number');
+ filterToBatch = batchParam;
+ delete caseFilterWithoutBatch[kBatchParamName];
+ }
+
+ for (const [caseParams, subcases] of builderIterateCasesWithSubcases(
+ this.testCases,
+ caseFilterWithoutBatch
+ )) {
+ // If batches are not used, yield just one case.
+ if (this.batchSize === 0 || subcases === undefined) {
+ yield this.makeCaseSpecific(caseParams, subcases);
+ continue;
+ }
+
+ // Same if there ends up being only one batch.
+ const subcaseArray = Array.from(subcases);
+ if (subcaseArray.length <= this.batchSize) {
+ yield this.makeCaseSpecific(caseParams, subcaseArray);
+ continue;
+ }
+
+ // There are multiple batches. Helper function for this case:
+ const makeCaseForBatch = (batch) => {
+ const sliceStart = batch * this.batchSize;
+ return this.makeCaseSpecific(
+ { ...caseParams, [kBatchParamName]: batch },
+ subcaseArray.slice(sliceStart, Math.min(subcaseArray.length, sliceStart + this.batchSize))
+ );
+ };
+
+ // If we filter to just one batch, yield it.
+ if (filterToBatch !== undefined) {
+ yield makeCaseForBatch(filterToBatch);
+ continue;
+ }
+
+ // Finally, if not, yield all of the batches.
+ for (let batch = 0; batch * this.batchSize < subcaseArray.length; ++batch) {
+ yield makeCaseForBatch(batch);
+ }
+ }
+ }
+}
+
+class RunCaseSpecific {
+
+
+
+
+
+
+
+
+
+
+ constructor(
+ testPath,
+ params,
+ isUnimplemented,
+ subcases,
+ fixture,
+ fn,
+ beforeFn,
+ testCreationStack)
+ {
+ this.id = { test: testPath, params: extractPublicParams(params) };
+ this.isUnimplemented = isUnimplemented;
+ this.params = params;
+ this.subcases = subcases;
+ this.fixture = fixture;
+ this.fn = fn;
+ this.beforeFn = beforeFn;
+ this.testCreationStack = testCreationStack;
+ }
+
+ computeSubcaseCount() {
+ if (this.subcases) {
+ let count = 0;
+ for (const _subcase of this.subcases) {
+ count++;
+ }
+ return count;
+ } else {
+ return 1;
+ }
+ }
+
+ async runTest(
+ rec,
+ sharedState,
+ params,
+ throwSkip,
+ expectedStatus)
+ {
+ try {
+ rec.beginSubCase();
+ if (expectedStatus === 'skip') {
+ throw new SkipTestCase('Skipped by expectations');
+ }
+
+ const inst = new this.fixture(sharedState, rec, params);
+ try {
+ await inst.init();
+ await this.fn(inst);
+ rec.passed();
+ } finally {
+ // Runs as long as constructor succeeded, even if initialization or the test failed.
+ await inst.finalize();
+ }
+ } catch (ex) {
+ // There was an exception from constructor, init, test, or finalize.
+ // An error from init or test may have been a SkipTestCase.
+ // An error from finalize may have been an eventualAsyncExpectation failure
+ // or unexpected validation/OOM error from the GPUDevice.
+ rec.threw(ex);
+ if (throwSkip && ex instanceof SkipTestCase) {
+ throw ex;
+ }
+ } finally {
+ try {
+ rec.endSubCase(expectedStatus);
+ } catch (ex) {
+ assert(ex instanceof UnexpectedPassError);
+ ex.message = `Testcase passed unexpectedly.`;
+ ex.stack = this.testCreationStack.stack;
+ rec.warn(ex);
+ }
+ }
+ }
+
+ async run(
+ rec,
+ selfQuery,
+ expectations)
+ {
+ const getExpectedStatus = (selfQueryWithSubParams) => {
+ let didSeeFail = false;
+ for (const exp of expectations) {
+ const ordering = compareQueries(exp.query, selfQueryWithSubParams);
+ if (ordering === Ordering.Unordered || ordering === Ordering.StrictSubset) {
+ continue;
+ }
+
+ switch (exp.expectation) {
+ // Skip takes precedence. If there is any expectation indicating a skip,
+ // signal it immediately.
+ case 'skip':
+ return 'skip';
+ case 'fail':
+ // Otherwise, indicate that we might expect a failure.
+ didSeeFail = true;
+ break;
+ default:
+ unreachable();
+ }
+ }
+ return didSeeFail ? 'fail' : 'pass';
+ };
+
+ const { testHeartbeatCallback, maxSubcasesInFlight } = globalTestConfig;
+ try {
+ rec.start();
+ const sharedState = this.fixture.MakeSharedState(rec, this.params);
+ try {
+ await sharedState.init();
+ if (this.beforeFn) {
+ await this.beforeFn(sharedState);
+ }
+ await sharedState.postInit();
+ testHeartbeatCallback();
+
+ let allPreviousSubcasesFinalizedPromise = Promise.resolve();
+ if (this.subcases) {
+ let totalCount = 0;
+ let skipCount = 0;
+
+ // If there are too many subcases in flight, starting the next subcase will register
+ // `resolvePromiseBlockingSubcase` and wait until `subcaseFinishedCallback` is called.
+ let subcasesInFlight = 0;
+ let resolvePromiseBlockingSubcase = undefined;
+ const subcaseFinishedCallback = () => {
+ subcasesInFlight -= 1;
+ // If there is any subcase waiting on a previous subcase to finish,
+ // unblock it now, and clear the resolve callback.
+ if (resolvePromiseBlockingSubcase) {
+ resolvePromiseBlockingSubcase();
+ resolvePromiseBlockingSubcase = undefined;
+ }
+ };
+
+ for (const subParams of this.subcases) {
+ // Make a recorder that will defer all calls until `allPreviousSubcasesFinalizedPromise`
+ // resolves. Waiting on `allPreviousSubcasesFinalizedPromise` ensures that
+ // logs from all the previous subcases have been flushed before flushing new logs.
+ const subcasePrefix = 'subcase: ' + stringifyPublicParams(subParams);
+ const subRec = new Proxy(rec, {
+ get: (target, k) => {
+ const prop = TestCaseRecorder.prototype[k];
+ if (typeof prop === 'function') {
+ testHeartbeatCallback();
+ return function (...args) {
+ void allPreviousSubcasesFinalizedPromise.then(() => {
+ // Prepend the subcase name to all error messages.
+ for (const arg of args) {
+ if (arg instanceof Error) {
+ try {
+ arg.message = subcasePrefix + '\n' + arg.message;
+ } catch {
+ // If that fails (e.g. on DOMException), try to put it in the stack:
+ let stack = subcasePrefix;
+ if (arg.stack) stack += '\n' + arg.stack;
+ try {
+ arg.stack = stack;
+ } catch {
+
+ // If that fails too, just silence it.
+ }}
+ }
+ }
+
+
+ const rv = prop.apply(target, args);
+ // Because this proxy executes functions in a deferred manner,
+ // it should never be used for functions that need to return a value.
+ assert(rv === undefined);
+ });
+ };
+ }
+ return prop;
+ }
+ });
+
+ const params = mergeParams(this.params, subParams);
+ const subcaseQuery = new TestQuerySingleCase(
+ selfQuery.suite,
+ selfQuery.filePathParts,
+ selfQuery.testPathParts,
+ params
+ );
+
+ // Limit the maximum number of subcases in flight.
+ if (subcasesInFlight >= maxSubcasesInFlight) {
+ await new Promise((resolve) => {
+ // There should only be one subcase waiting at a time.
+ assert(resolvePromiseBlockingSubcase === undefined);
+ resolvePromiseBlockingSubcase = resolve;
+ });
+ }
+
+ subcasesInFlight += 1;
+ // Runs async without waiting so that subsequent subcases can start.
+ // All finalization steps will be waited on at the end of the testcase.
+ const finalizePromise = this.runTest(
+ subRec,
+ sharedState,
+ params,
+ /* throwSkip */true,
+ getExpectedStatus(subcaseQuery)
+ ).
+ then(() => {
+ subRec.info(new Error('OK'));
+ }).
+ catch((ex) => {
+ if (ex instanceof SkipTestCase) {
+ // Convert SkipTestCase to info messages
+ ex.message = 'subcase skipped: ' + ex.message;
+ subRec.info(ex);
+ ++skipCount;
+ } else {
+ // Since we are catching all error inside runTest(), this should never happen
+ subRec.threw(ex);
+ }
+ }).
+ finally(subcaseFinishedCallback);
+
+ allPreviousSubcasesFinalizedPromise = allPreviousSubcasesFinalizedPromise.then(
+ () => finalizePromise
+ );
+ ++totalCount;
+ }
+
+ // Wait for all subcases to finalize and report their results.
+ await allPreviousSubcasesFinalizedPromise;
+
+ if (skipCount === totalCount) {
+ rec.skipped(new SkipTestCase('all subcases were skipped'));
+ }
+ } else {
+ await this.runTest(
+ rec,
+ sharedState,
+ this.params,
+ /* throwSkip */false,
+ getExpectedStatus(selfQuery)
+ );
+ }
+ } finally {
+ testHeartbeatCallback();
+ // Runs as long as the shared state constructor succeeded, even if initialization or a test failed.
+ await sharedState.finalize();
+ testHeartbeatCallback();
+ }
+ } catch (ex) {
+ // There was an exception from sharedState/fixture constructor, init, beforeFn, or test.
+ // An error from beforeFn may have been SkipTestCase.
+ // An error from finalize may have been an eventualAsyncExpectation failure
+ // or unexpected validation/OOM error from the GPUDevice.
+ rec.threw(ex);
+ } finally {
+ rec.finish();
+
+ const msg = {
+ q: selfQuery.toString(),
+ timems: rec.result.timems,
+ nonskippedSubcaseCount: rec.nonskippedSubcaseCount
+ };
+ logToWebsocket(JSON.stringify(msg));
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js
new file mode 100644
index 0000000000..81a15cf712
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/test_suite_listing.js
@@ -0,0 +1,6 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // 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).
+export {}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js
new file mode 100644
index 0000000000..8f1e6bad66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/tree.js
@@ -0,0 +1,671 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { loadMetadataForSuite } from '../framework/metadata.js';import { globalTestConfig } from '../framework/test_config.js';
+import { assert, now } from '../util/util.js';
+
+
+
+import { comparePublicParamsPaths, compareQueries, Ordering } from './query/compare.js';
+import {
+
+ TestQueryMultiCase,
+ TestQuerySingleCase,
+ TestQueryMultiFile,
+ TestQueryMultiTest } from
+'./query/query.js';
+import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './query/separators.js';
+import { stringifySingleParam } from './query/stringify_params.js';
+import { StacklessError } from './util.js';
+
+// `loadTreeForQuery()` loads a TestTree for a given queryToLoad.
+// The resulting tree is a linked-list all the way from `suite:*` to queryToLoad,
+// and under queryToLoad is a tree containing every case matched by queryToLoad.
+//
+// `subqueriesToExpand` influences the `collapsible` flag on nodes in the resulting tree.
+// A node is considered "collapsible" if none of the subqueriesToExpand is a StrictSubset
+// of that node.
+//
+// In WebKit/Blink-style web_tests, an expectation file marks individual cts.https.html "variants
+// as "Failure", "Crash", etc. By passing in the list of expectations as the subqueriesToExpand,
+// we can programmatically subdivide the cts.https.html "variants" list to be able to implement
+// arbitrarily-fine suppressions (instead of having to suppress entire test files, which would
+// lose a lot of coverage).
+//
+// `iterateCollapsedNodes()` produces the list of queries for the variants list.
+//
+// Though somewhat complicated, this system has important benefits:
+// - Avoids having to suppress entire test files, which would cause large test coverage loss.
+// - Minimizes the number of page loads needed for fine-grained suppressions.
+// (In the naive case, we could do one page load per test case - but the test suite would
+// take impossibly long to run.)
+// - Enables developers to put any number of tests in one file as appropriate, without worrying
+// about expectation granularity.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * When iterating through "collapsed" tree nodes, indicates how many "query levels" to traverse
+ * through before starting to collapse nodes.
+ *
+ * Corresponds with TestQueryLevel, but excludes 4 (SingleCase):
+ * - 1 = MultiFile. Expands so every file is in the collapsed tree.
+ * - 2 = MultiTest. Expands so every test is in the collapsed tree.
+ * - 3 = MultiCase. Expands so every case is in the collapsed tree (i.e. collapsing disabled).
+ */
+
+
+export class TestTree {
+ /**
+ * The `queryToLoad` that this test tree was created for.
+ * Test trees are always rooted at `suite:*`, but they only contain nodes that fit
+ * within `forQuery`.
+ *
+ * This is used for `iterateCollapsedNodes` which only starts collapsing at the next
+ * `TestQueryLevel` after `forQuery`.
+ */
+
+
+
+ constructor(forQuery, root) {
+ this.forQuery = forQuery;
+ this.root = root;
+ assert(
+ root.query.level === 1 && root.query.depthInLevel === 0,
+ 'TestTree root must be the root (suite:*)'
+ );
+ }
+
+ static async create(
+ forQuery,
+ root,
+ maxChunkTime)
+ {
+ const suite = forQuery.suite;
+
+ let chunking = undefined;
+ if (Number.isFinite(maxChunkTime)) {
+ const metadata = loadMetadataForSuite(`./src/${suite}`);
+ assert(metadata !== null, `metadata for ${suite} is missing, but maxChunkTime was requested`);
+ chunking = { metadata, maxChunkTime };
+ }
+ await TestTree.propagateCounts(root, chunking);
+
+ return new TestTree(forQuery, root);
+ }
+
+ /**
+ * Iterate through the leaves of a version of the tree which has been pruned to exclude
+ * subtrees which:
+ * - are at a deeper `TestQueryLevel` than `this.forQuery`, and
+ * - were not a `Ordering.StrictSubset` of any of the `subqueriesToExpand` during tree creation.
+ */
+ iterateCollapsedNodes({
+ includeIntermediateNodes = false,
+ includeEmptySubtrees = false,
+ alwaysExpandThroughLevel
+
+
+
+
+
+
+
+ }) {
+ const expandThroughLevel = Math.max(this.forQuery.level, alwaysExpandThroughLevel);
+ return TestTree.iterateSubtreeNodes(this.root, {
+ includeIntermediateNodes,
+ includeEmptySubtrees,
+ expandThroughLevel
+ });
+ }
+
+ iterateLeaves() {
+ return TestTree.iterateSubtreeLeaves(this.root);
+ }
+
+ /**
+ * Dissolve nodes which have only one child, e.g.:
+ * a,* { a,b,* { a,b:* { ... } } }
+ * collapses down into:
+ * a,* { a,b:* { ... } }
+ * which is less needlessly verbose when displaying the tree in the standalone runner.
+ */
+ dissolveSingleChildTrees() {
+ const newRoot = dissolveSingleChildTrees(this.root);
+ assert(newRoot === this.root);
+ }
+
+ toString() {
+ return TestTree.subtreeToString('(root)', this.root, '');
+ }
+
+ static *iterateSubtreeNodes(
+ subtree,
+ opts)
+
+
+
+
+ {
+ if (opts.includeIntermediateNodes) {
+ yield subtree;
+ }
+
+ for (const [, child] of subtree.children) {
+ if ('children' in child) {
+ // Is a subtree
+ const collapsible = child.collapsible && child.query.level > opts.expandThroughLevel;
+ if (child.children.size > 0 && !collapsible) {
+ yield* TestTree.iterateSubtreeNodes(child, opts);
+ } else if (child.children.size > 0 || opts.includeEmptySubtrees) {
+ // Don't yield empty subtrees (e.g. files with no tests) unless includeEmptySubtrees
+ yield child;
+ }
+ } else {
+ // Is a leaf
+ yield child;
+ }
+ }
+ }
+
+ static *iterateSubtreeLeaves(subtree) {
+ for (const [, child] of subtree.children) {
+ if ('children' in child) {
+ yield* TestTree.iterateSubtreeLeaves(child);
+ } else {
+ yield child;
+ }
+ }
+ }
+
+ /** Propagate the subtreeTODOs/subtreeTests state upward from leaves to parent nodes. */
+ static async propagateCounts(
+ subtree,
+ chunking)
+ {
+ subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0, totalTimeMS: 0 };
+ subtree.subcaseCount = 0;
+ for (const [, child] of subtree.children) {
+ if ('children' in child) {
+ const counts = await TestTree.propagateCounts(child, chunking);
+ subtree.subtreeCounts.tests += counts.tests;
+ subtree.subtreeCounts.nodesWithTODO += counts.nodesWithTODO;
+ subtree.subtreeCounts.totalTimeMS += counts.totalTimeMS;
+ subtree.subcaseCount += counts.subcaseCount;
+ } else {
+ subtree.subcaseCount = child.subcaseCount;
+ }
+ }
+
+ // If we're chunking based on a maxChunkTime, then at each
+ // TestQueryMultiCase node of the tree we look at its total time. If the
+ // total time is larger than the maxChunkTime, we set collapsible=false to
+ // make sure it gets split up in the output. Note:
+ // - TestQueryMultiTest and higher nodes are never set to collapsible anyway, so we ignore them.
+ // - TestQuerySingleCase nodes can't be collapsed, so we ignore them.
+ if (chunking && subtree.query instanceof TestQueryMultiCase) {
+ const testLevelQuery = new TestQueryMultiCase(
+ subtree.query.suite,
+ subtree.query.filePathParts,
+ subtree.query.testPathParts,
+ {}
+ ).toString();
+
+ const metadata = chunking.metadata;
+
+ const subcaseTiming = metadata[testLevelQuery]?.subcaseMS;
+ if (subcaseTiming !== undefined) {
+ const totalTiming = subcaseTiming * subtree.subcaseCount;
+ subtree.subtreeCounts.totalTimeMS = totalTiming;
+ if (totalTiming > chunking.maxChunkTime) {
+ subtree.collapsible = false;
+ }
+ }
+ }
+
+ return { ...subtree.subtreeCounts, subcaseCount: subtree.subcaseCount ?? 0 };
+ }
+
+ /** Displays counts in the format `(Nodes with TODOs) / (Total test count)`. */
+ static countsToString(tree) {
+ if (tree.subtreeCounts) {
+ return `${tree.subtreeCounts.nodesWithTODO} / ${tree.subtreeCounts.tests}`;
+ } else {
+ return '';
+ }
+ }
+
+ static subtreeToString(name, tree, indent) {
+ const collapsible = 'run' in tree ? '>' : tree.collapsible ? '+' : '-';
+ let s =
+ indent +
+ `${collapsible} ${TestTree.countsToString(tree)} ${JSON.stringify(name)} => ${tree.query}`;
+ if ('children' in tree) {
+ if (tree.description !== undefined) {
+ s += `\n${indent} | ${JSON.stringify(tree.description)}`;
+ }
+
+ for (const [name, child] of tree.children) {
+ s += '\n' + TestTree.subtreeToString(name, child, indent + ' ');
+ }
+ }
+ return s;
+ }
+}
+
+// MAINTENANCE_TODO: Consider having subqueriesToExpand actually impact the depth-order of params
+// in the tree.
+export async function loadTreeForQuery(
+loader,
+queryToLoad,
+{
+ subqueriesToExpand,
+ maxChunkTime = Infinity
+})
+{
+ const suite = queryToLoad.suite;
+ const specs = await loader.listing(suite);
+
+ const subqueriesToExpandEntries = Array.from(subqueriesToExpand.entries());
+ const seenSubqueriesToExpand = new Array(subqueriesToExpand.length);
+ seenSubqueriesToExpand.fill(false);
+
+ const isCollapsible = (subquery) =>
+ subqueriesToExpandEntries.every(([i, toExpand]) => {
+ const ordering = compareQueries(toExpand, subquery);
+
+ // If toExpand == subquery, no expansion is needed (but it's still "seen").
+ if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true;
+ return ordering !== Ordering.StrictSubset;
+ });
+
+ // L0 = suite-level, e.g. suite:*
+ // L1 = file-level, e.g. suite:a,b:*
+ // L2 = test-level, e.g. suite:a,b:c,d:*
+ // L3 = case-level, e.g. suite:a,b:c,d:
+ let foundCase = false;
+ // L0 is suite:*
+ const subtreeL0 = makeTreeForSuite(suite, isCollapsible);
+
+ const imports_start = now();
+ const pEntriesWithImports = []; // Promise<entry with importedSpec>[]
+ for (const entry of specs) {
+ if (entry.file.length === 0 && 'readme' in entry) {
+ // Suite-level readme.
+ setSubtreeDescriptionAndCountTODOs(subtreeL0, entry.readme);
+ continue;
+ }
+
+ {
+ const queryL1 = new TestQueryMultiFile(suite, entry.file);
+ const orderingL1 = compareQueries(queryL1, queryToLoad);
+ if (orderingL1 === Ordering.Unordered) {
+ // File path is not matched by this query.
+ continue;
+ }
+ }
+
+ // We're going to be fetching+importing a bunch of things, so do it in async.
+ const pEntryWithImport = (async () => {
+ if ('readme' in entry) {
+ return entry;
+ } else {
+ return {
+ ...entry,
+ importedSpec: await loader.importSpecFile(queryToLoad.suite, entry.file)
+ };
+ }
+ })();
+
+ const kForceSerialImporting = false;
+ if (kForceSerialImporting) {
+ await pEntryWithImport;
+ }
+ pEntriesWithImports.push(pEntryWithImport);
+ }
+
+ const entriesWithImports = await Promise.all(pEntriesWithImports);
+ if (globalTestConfig.frameworkDebugLog) {
+ const imported_time = performance.now() - imports_start;
+ globalTestConfig.frameworkDebugLog(
+ `Imported importedSpecFiles[${entriesWithImports.length}] in ${imported_time}ms.`
+ );
+ }
+
+ for (const entry of entriesWithImports) {
+ if ('readme' in entry) {
+ // Entry is a README that is an ancestor or descendant of the query.
+ // (It's included for display in the standalone runner.)
+
+ // readmeSubtree is suite:a,b,*
+ // (This is always going to dedup with a file path, if there are any test spec files under
+ // the directory that has the README).
+ const readmeSubtree = addSubtreeForDirPath(
+ subtreeL0,
+ entry.file,
+ isCollapsible
+ );
+ setSubtreeDescriptionAndCountTODOs(readmeSubtree, entry.readme);
+ continue;
+ }
+
+ // Entry is a spec file.
+ const spec = entry.importedSpec;
+ // subtreeL1 is suite:a,b:*
+ const subtreeL1 = addSubtreeForFilePath(
+ subtreeL0,
+ entry.file,
+ isCollapsible
+ );
+ setSubtreeDescriptionAndCountTODOs(subtreeL1, spec.description);
+
+ let groupHasTests = false;
+ for (const t of spec.g.iterate()) {
+ groupHasTests = true;
+ {
+ const queryL2 = new TestQueryMultiCase(suite, entry.file, t.testPath, {});
+ const orderingL2 = compareQueries(queryL2, queryToLoad);
+ if (orderingL2 === Ordering.Unordered) {
+ // Test path is not matched by this query.
+ continue;
+ }
+ }
+
+ // subtreeL2 is suite:a,b:c,d:*
+ const subtreeL2 = addSubtreeForTestPath(
+ subtreeL1,
+ t.testPath,
+ t.testCreationStack,
+ isCollapsible
+ );
+ // This is 1 test. Set tests=1 then count TODOs.
+ subtreeL2.subtreeCounts ??= { tests: 1, nodesWithTODO: 0, totalTimeMS: 0 };
+ if (t.description) setSubtreeDescriptionAndCountTODOs(subtreeL2, t.description);
+
+ let caseFilter = null;
+ if ('params' in queryToLoad) {
+ caseFilter = queryToLoad.params;
+ }
+
+ // MAINTENANCE_TODO: If tree generation gets too slow, avoid actually iterating the cases in a
+ // file if there's no need to (based on the subqueriesToExpand).
+ for (const c of t.iterate(caseFilter)) {
+ // iterate() guarantees c's query is equal to or a subset of queryToLoad.
+
+ if (queryToLoad instanceof TestQuerySingleCase) {
+ // A subset is OK if it's TestQueryMultiCase, but for SingleCase it must match exactly.
+ const ordering = comparePublicParamsPaths(c.id.params, queryToLoad.params);
+ if (ordering !== Ordering.Equal) {
+ continue;
+ }
+ }
+
+ // Leaf for case is suite:a,b:c,d:x=1;y=2
+ addLeafForCase(subtreeL2, c, isCollapsible);
+ foundCase = true;
+ }
+ }
+ if (!groupHasTests && !subtreeL1.subtreeCounts) {
+ throw new StacklessError(
+ `${subtreeL1.query} has no tests - it must have "TODO" in its description`
+ );
+ }
+ }
+
+ for (const [i, sq] of subqueriesToExpandEntries) {
+ const subquerySeen = seenSubqueriesToExpand[i];
+ if (!subquerySeen) {
+ throw new StacklessError(
+ `subqueriesToExpand entry did not match anything \
+(could be wrong, or could be redundant with a previous subquery):\n ${sq.toString()}`
+ );
+ }
+ }
+ assert(foundCase, `Query \`${queryToLoad.toString()}\` does not match any cases`);
+
+ return TestTree.create(queryToLoad, subtreeL0, maxChunkTime);
+}
+
+function setSubtreeDescriptionAndCountTODOs(
+subtree,
+description)
+{
+ assert(subtree.description === undefined);
+ subtree.description = description.trim();
+ subtree.subtreeCounts ??= { tests: 0, nodesWithTODO: 0, totalTimeMS: 0 };
+ if (subtree.description.indexOf('TODO') !== -1) {
+ subtree.subtreeCounts.nodesWithTODO++;
+ }
+}
+
+function makeTreeForSuite(
+suite,
+isCollapsible)
+{
+ const query = new TestQueryMultiFile(suite, []);
+ return {
+ readableRelativeName: suite + kBigSeparator,
+ query,
+ children: new Map(),
+ collapsible: isCollapsible(query)
+ };
+}
+
+function addSubtreeForDirPath(
+tree,
+file,
+isCollapsible)
+{
+ const subqueryFile = [];
+ // To start, tree is suite:*
+ // This loop goes from that -> suite:a,* -> suite:a,b,*
+ for (const part of file) {
+ subqueryFile.push(part);
+ tree = getOrInsertSubtree(part, tree, () => {
+ const query = new TestQueryMultiFile(tree.query.suite, subqueryFile);
+ return {
+ readableRelativeName: part + kPathSeparator + kWildcard,
+ query,
+ collapsible: isCollapsible(query)
+ };
+ });
+ }
+ return tree;
+}
+
+function addSubtreeForFilePath(
+tree,
+file,
+isCollapsible)
+{
+ // To start, tree is suite:*
+ // This goes from that -> suite:a,* -> suite:a,b,*
+ tree = addSubtreeForDirPath(tree, file, isCollapsible);
+ // This goes from that -> suite:a,b:*
+ const subtree = getOrInsertSubtree('', tree, () => {
+ const query = new TestQueryMultiTest(tree.query.suite, tree.query.filePathParts, []);
+ assert(file.length > 0, 'file path is empty');
+ return {
+ readableRelativeName: file[file.length - 1] + kBigSeparator + kWildcard,
+ query,
+ collapsible: isCollapsible(query)
+ };
+ });
+ return subtree;
+}
+
+function addSubtreeForTestPath(
+tree,
+test,
+testCreationStack,
+isCollapsible)
+{
+ const subqueryTest = [];
+ // To start, tree is suite:a,b:*
+ // This loop goes from that -> suite:a,b:c,* -> suite:a,b:c,d,*
+ for (const part of test) {
+ subqueryTest.push(part);
+ tree = getOrInsertSubtree(part, tree, () => {
+ const query = new TestQueryMultiTest(
+ tree.query.suite,
+ tree.query.filePathParts,
+ subqueryTest
+ );
+ return {
+ readableRelativeName: part + kPathSeparator + kWildcard,
+ query,
+ collapsible: isCollapsible(query)
+ };
+ });
+ }
+ // This goes from that -> suite:a,b:c,d:*
+ return getOrInsertSubtree('', tree, () => {
+ const query = new TestQueryMultiCase(
+ tree.query.suite,
+ tree.query.filePathParts,
+ subqueryTest,
+ {}
+ );
+ assert(subqueryTest.length > 0, 'subqueryTest is empty');
+ return {
+ readableRelativeName: subqueryTest[subqueryTest.length - 1] + kBigSeparator + kWildcard,
+ kWildcard,
+ query,
+ testCreationStack,
+ collapsible: isCollapsible(query)
+ };
+ });
+}
+
+function addLeafForCase(
+tree,
+t,
+checkCollapsible)
+{
+ const query = tree.query;
+ let name = '';
+ const subqueryParams = {};
+
+ // To start, tree is suite:a,b:c,d:*
+ // This loop goes from that -> suite:a,b:c,d:x=1;* -> suite:a,b:c,d:x=1;y=2;*
+ for (const [k, v] of Object.entries(t.id.params)) {
+ name = stringifySingleParam(k, v);
+ subqueryParams[k] = v;
+
+ tree = getOrInsertSubtree(name, tree, () => {
+ const subquery = new TestQueryMultiCase(
+ query.suite,
+ query.filePathParts,
+ query.testPathParts,
+ subqueryParams
+ );
+ return {
+ readableRelativeName: name + kParamSeparator + kWildcard,
+ query: subquery,
+ collapsible: checkCollapsible(subquery)
+ };
+ });
+ }
+
+ // This goes from that -> suite:a,b:c,d:x=1;y=2
+ const subquery = new TestQuerySingleCase(
+ query.suite,
+ query.filePathParts,
+ query.testPathParts,
+ subqueryParams
+ );
+ checkCollapsible(subquery); // mark seenSubqueriesToExpand
+ insertLeaf(tree, subquery, t);
+}
+
+function getOrInsertSubtree(
+key,
+parent,
+createSubtree)
+{
+ let v;
+ const child = parent.children.get(key);
+ if (child !== undefined) {
+ assert('children' in child); // Make sure cached subtree is not actually a leaf
+ v = child;
+ } else {
+ v = { ...createSubtree(), children: new Map() };
+ parent.children.set(key, v);
+ }
+ return v;
+}
+
+function insertLeaf(parent, query, t) {
+ const leaf = {
+ readableRelativeName: readableNameForCase(query),
+ query,
+ run: (rec, expectations) => t.run(rec, query, expectations || []),
+ isUnimplemented: t.isUnimplemented,
+ subcaseCount: t.computeSubcaseCount()
+ };
+
+ // This is a leaf (e.g. s:f:t:x=1;* -> s:f:t:x=1). The key is always ''.
+ const key = '';
+ assert(!parent.children.has(key), `Duplicate testcase: ${query}`);
+ parent.children.set(key, leaf);
+}
+
+function dissolveSingleChildTrees(tree) {
+ if ('children' in tree) {
+ const shouldDissolveThisTree =
+ tree.children.size === 1 && tree.query.depthInLevel !== 0 && tree.description === undefined;
+ if (shouldDissolveThisTree) {
+ // Loops exactly once
+ for (const [, child] of tree.children) {
+ // Recurse on child
+ return dissolveSingleChildTrees(child);
+ }
+ }
+
+ for (const [k, child] of tree.children) {
+ // Recurse on each child
+ const newChild = dissolveSingleChildTrees(child);
+ if (newChild !== child) {
+ tree.children.set(k, newChild);
+ }
+ }
+ }
+ return tree;
+}
+
+/** Generate a readable relative name for a case (used in standalone). */
+function readableNameForCase(query) {
+ const paramsKeys = Object.keys(query.params);
+ if (paramsKeys.length === 0) {
+ return query.testPathParts[query.testPathParts.length - 1] + kBigSeparator;
+ } else {
+ const lastKey = paramsKeys[paramsKeys.length - 1];
+ return stringifySingleParam(lastKey, query.params[lastKey]);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/util.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/util.js
new file mode 100644
index 0000000000..6e6125474d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/util.js
@@ -0,0 +1,10 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Error without a stack, which can be used to fatally exit from `tool/` scripts with a
+ * user-friendly message (and no confusing stack).
+ */export class StacklessError extends Error {constructor(message) {
+ super(message);
+ this.stack = undefined;
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js
new file mode 100644
index 0000000000..7a632d5635
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/version.js
@@ -0,0 +1,3 @@
+// AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
+
+export const version = '41f89e77b67e6b66cb017be4e00235a0a9429ca7';
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js b/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js
new file mode 100644
index 0000000000..7a8f92b1a8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/internal/websocket_logger.js
@@ -0,0 +1,52 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * - 'uninitialized' means we haven't tried to connect yet
+ * - Promise means it's pending
+ * - 'failed' means it failed (this is the most common case, where the logger isn't running)
+ * - WebSocket means it succeeded
+ */let connection = 'uninitialized';
+
+/**
+ * Log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`.
+ *
+ * This does nothing if a connection couldn't be established on the first call.
+ */
+export function logToWebsocket(msg) {
+ if (connection === 'failed') {
+ return;
+ }
+
+ if (connection === 'uninitialized') {
+ connection = new Promise((resolve) => {
+ if (typeof WebSocket === 'undefined') {
+ resolve('failed');
+ return;
+ }
+
+ const ws = new WebSocket('ws://localhost:59497/optional_cts_websocket_logger');
+ ws.onopen = () => {
+ resolve(ws);
+ };
+ ws.onerror = () => {
+ connection = 'failed';
+ resolve('failed');
+ };
+ ws.onclose = () => {
+ connection = 'failed';
+ resolve('failed');
+ };
+ });
+ void connection.then((resolved) => {
+ connection = resolved;
+ });
+ }
+
+ void (async () => {
+ // connection may be a promise or a value here. Either is OK to await.
+ const ws = await connection;
+ if (ws !== 'failed') {
+ ws.send(msg);
+ }
+ })();
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js
new file mode 100644
index 0000000000..139c3bc29f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js
@@ -0,0 +1,129 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/let windowURL = undefined;function getWindowURL() {if (windowURL === undefined) {
+ windowURL = new URL(window.location.toString());
+ }
+ return windowURL;
+}
+
+export function optionEnabled(
+opt,
+searchParams = getWindowURL().searchParams)
+{
+ const val = searchParams.get(opt);
+ return val !== null && val !== '0';
+}
+
+export function optionString(
+opt,
+searchParams = getWindowURL().searchParams)
+{
+ return searchParams.get(opt) || '';
+}
+
+/**
+ * The possible options for the tests.
+ */
+
+
+
+
+
+
+
+
+export const kDefaultCTSOptions = {
+ worker: false,
+ debug: true,
+ compatibility: false,
+ unrollConstEvalLoops: false,
+ powerPreference: ''
+};
+
+/**
+ * Extra per option info.
+ */
+
+
+
+
+
+
+/**
+ * Type for info for every option. This definition means adding an option
+ * will generate a compile time error if no extra info is provided.
+ */
+
+
+/**
+ * Options to the CTS.
+ */
+export const kCTSOptionsInfo = {
+ worker: { description: 'run in a worker' },
+ debug: { description: 'show more info' },
+ compatibility: { description: 'run in compatibility mode' },
+ unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' },
+ powerPreference: {
+ description: 'set default powerPreference for some tests',
+ parser: optionString,
+ selectValueDescriptions: [
+ { value: '', description: 'default' },
+ { value: 'low-power', description: 'low-power' },
+ { value: 'high-performance', description: 'high-performance' }]
+
+ }
+};
+
+/**
+ * Converts camel case to snake case.
+ * Examples:
+ * fooBar -> foo_bar
+ * parseHTMLFile -> parse_html_file
+ */
+export function camelCaseToSnakeCase(id) {
+ return id.
+ replace(/(.)([A-Z][a-z]+)/g, '$1_$2').
+ replace(/([a-z0-9])([A-Z])/g, '$1_$2').
+ toLowerCase();
+}
+
+/**
+ * Creates a Options from search parameters.
+ */
+function getOptionsInfoFromSearchString(
+optionsInfos,
+searchString)
+{
+ const searchParams = new URLSearchParams(searchString);
+ const optionValues = {};
+ for (const [optionName, info] of Object.entries(optionsInfos)) {
+ const parser = info.parser || optionEnabled;
+ optionValues[optionName] = parser(camelCaseToSnakeCase(optionName), searchParams);
+ }
+ return optionValues;
+}
+
+/**
+ * Given a test query string in the form of `suite:foo,bar,moo&opt1=val1&opt2=val2
+ * returns the query and the options.
+ */
+export function parseSearchParamLikeWithOptions(
+optionsInfos,
+query)
+
+
+
+{
+ const searchString = query.includes('q=') || query.startsWith('?') ? query : `q=${query}`;
+ const queries = new URLSearchParams(searchString).getAll('q');
+ const options = getOptionsInfoFromSearchString(optionsInfos, searchString);
+ return { queries, options };
+}
+
+/**
+ * Given a test query string in the form of `suite:foo,bar,moo&opt1=val1&opt2=val2
+ * returns the query and the common options.
+ */
+export function parseSearchParamLikeWithCTSOptions(query) {
+ return parseSearchParamLikeWithOptions(kCTSOptionsInfo, query);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js
new file mode 100644
index 0000000000..a0f13c54af
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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 { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js';
+import { assert } from '../../util/util.js';
+
+
+
+// Should be DedicatedWorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
+
+
+
+const loader = new DefaultTestFileLoader();
+
+setBaseResourcePath('../../../resources');
+
+self.onmessage = async (ev) => {
+ const query = ev.data.query;
+ const expectations = ev.data.expectations;
+ const ctsOptions = ev.data.ctsOptions;
+
+ 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 testcases = Array.from(await loader.loadCases(parseQuery(query)));
+ assert(testcases.length === 1, 'worker query resulted in != 1 cases');
+
+ const testcase = testcases[0];
+ const [rec, result] = log.record(testcase.query.toString());
+ await testcase.run(rec, expectations);
+
+ self.postMessage({ query, result });
+}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js
new file mode 100644
index 0000000000..1d65394180
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js
@@ -0,0 +1,49 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { LogMessageWithStack } from '../../internal/logging/log_message.js';
+
+
+import { kDefaultCTSOptions } from './options.js';
+
+export class TestWorker {
+
+
+ resolvers = new Map();
+
+ constructor(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 = ev.data.query;
+ const result = ev.data.result;
+ if (result.logs) {
+ for (const l of result.logs) {
+ Object.setPrototypeOf(l, LogMessageWithStack.prototype);
+ }
+ }
+ this.resolvers.get(query)(result);
+
+ // 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,
+ query,
+ expectations = [])
+ {
+ this.worker.postMessage({
+ query,
+ expectations,
+ ctsOptions: this.ctsOptions
+ });
+ const workerResult = await new Promise((resolve) => {
+ this.resolvers.set(query, resolve);
+ });
+ rec.injectResult(workerResult);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js
new file mode 100644
index 0000000000..97c6a3886e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/wpt.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // Implements the wpt-embedded test runner (see also: wpt/cts.https.html).
+import { globalTestConfig } from '../framework/test_config.js';import { DefaultTestFileLoader } from '../internal/file_loader.js';
+import { prettyPrintLog } from '../internal/logging/log_message.js';
+import { Logger } from '../internal/logging/logger.js';
+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';
+
+// testharness.js API (https://web-platform-tests.org/writing-tests/testharness-api.html)
+
+
+
+
+
+
+
+
+
+
+
+
+setup({
+ // It's convenient for us to asynchronously add tests to the page. Prevent done() from being
+ // called implicitly when the page is finished loading.
+ explicit_done: true
+});
+
+void (async () => {
+ const workerEnabled = optionEnabled('worker');
+ const worker = workerEnabled ? new TestWorker() : undefined;
+
+ globalTestConfig.unrollConstEvalLoops = optionEnabled('unroll_const_eval_loops');
+
+ const failOnWarnings =
+ typeof shouldWebGPUCTSFailOnWarnings !== 'undefined' && (await shouldWebGPUCTSFailOnWarnings);
+
+ const loader = new DefaultTestFileLoader();
+ const qs = new URLSearchParams(window.location.search).getAll('q');
+ assert(qs.length === 1, 'currently, there must be exactly one ?q=');
+ const filterQuery = parseQuery(qs[0]);
+ const testcases = await loader.loadCases(filterQuery);
+
+ const expectations =
+ typeof loadWebGPUExpectations !== 'undefined' ?
+ parseExpectationsForTestQuery(
+ await loadWebGPUExpectations,
+ filterQuery,
+ new URL(window.location.href)
+ ) :
+ [];
+
+ const log = new Logger();
+
+ for (const testcase of testcases) {
+ const name = testcase.query.toString();
+ // For brevity, display the case name "relative" to the ?q= path.
+ const shortName = relativeQueryString(filterQuery, testcase.query) || '(case)';
+
+ const wpt_fn = async () => {
+ const [rec, res] = log.record(name);
+ if (worker) {
+ await worker.run(rec, name, expectations);
+ } else {
+ await testcase.run(rec, expectations);
+ }
+
+ // Unfortunately, it seems not possible to surface any logs for warn/skip.
+ if (res.status === 'fail' || res.status === 'warn' && failOnWarnings) {
+ const logs = (res.logs ?? []).map(prettyPrintLog);
+ assert_unreached('\n' + logs.join('\n') + '\n');
+ }
+ };
+
+ promise_test(wpt_fn, shortName);
+ }
+
+ done();
+})(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/collect_garbage.js b/testing/web-platform/mozilla/tests/webgpu/common/util/collect_garbage.js
new file mode 100644
index 0000000000..9b7ef10af5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/collect_garbage.js
@@ -0,0 +1,58 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { resolveOnTimeout } from './util.js';
+
+
+/**
+ * Attempts to trigger JavaScript garbage collection, either using explicit methods if exposed
+ * (may be available in testing environments with special browser runtime flags set), or using
+ * some weird tricks to incur GC pressure. Adopted from the WebGL CTS.
+ */
+export async function attemptGarbageCollection() {
+
+ const w = globalThis;
+ if (w.GCController) {
+ w.GCController.collect();
+ return;
+ }
+
+ if (w.opera && w.opera.collect) {
+ w.opera.collect();
+ return;
+ }
+
+ try {
+ w.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils).
+ garbageCollect();
+ return;
+ } catch (e) {
+
+ // ignore any failure
+ }
+ if (w.gc) {
+ w.gc();
+ return;
+ }
+
+ if (w.CollectGarbage) {
+ w.CollectGarbage();
+ return;
+ }
+
+ let i;
+ function gcRec(n) {
+ if (n < 1) return;
+
+ let temp = { i: 'ab' + i + i / 100000 };
+
+ temp = temp + 'foo';
+ temp; // dummy use of unused variable
+ gcRec(n - 1);
+ }
+ for (i = 0; i < 1000; i++) {
+ gcRec(10);
+ }
+
+ return resolveOnTimeout(35); // Let the event loop run a few frames in case it helps.
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/colors.js b/testing/web-platform/mozilla/tests/webgpu/common/util/colors.js
new file mode 100644
index 0000000000..d071665138
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/colors.js
@@ -0,0 +1,127 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * The interface used for formatting strings to contain color metadata.
+ *
+ * Use the interface properties to construct a style, then use the
+ * `(s: string): string` function to format the provided string with the given
+ * style.
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * The interface used for formatting strings with color metadata.
+ *
+ * Currently Colors will use the 'ansi-colors' module if it can be loaded.
+ * If it cannot be loaded, then the Colors implementation is a straight pass-through.
+ *
+ * Colors may also be a no-op if the current environment does not support colors.
+ */
+export let Colors;
+
+try {
+
+ Colors = require('ansi-colors');
+} catch {
+ const passthrough = (s) => s;
+ passthrough.enabled = false;
+ passthrough.reset = passthrough;
+ passthrough.bold = passthrough;
+ passthrough.dim = passthrough;
+ passthrough.italic = passthrough;
+ passthrough.underline = passthrough;
+ passthrough.inverse = passthrough;
+ passthrough.hidden = passthrough;
+ passthrough.strikethrough = passthrough;
+ passthrough.black = passthrough;
+ passthrough.red = passthrough;
+ passthrough.green = passthrough;
+ passthrough.yellow = passthrough;
+ passthrough.blue = passthrough;
+ passthrough.magenta = passthrough;
+ passthrough.cyan = passthrough;
+ passthrough.white = passthrough;
+ passthrough.gray = passthrough;
+ passthrough.grey = passthrough;
+ passthrough.blackBright = passthrough;
+ passthrough.redBright = passthrough;
+ passthrough.greenBright = passthrough;
+ passthrough.yellowBright = passthrough;
+ passthrough.blueBright = passthrough;
+ passthrough.magentaBright = passthrough;
+ passthrough.cyanBright = passthrough;
+ passthrough.whiteBright = passthrough;
+ passthrough.bgBlack = passthrough;
+ passthrough.bgRed = passthrough;
+ passthrough.bgGreen = passthrough;
+ passthrough.bgYellow = passthrough;
+ passthrough.bgBlue = passthrough;
+ passthrough.bgMagenta = passthrough;
+ passthrough.bgCyan = passthrough;
+ passthrough.bgWhite = passthrough;
+ passthrough.bgBlackBright = passthrough;
+ passthrough.bgRedBright = passthrough;
+ passthrough.bgGreenBright = passthrough;
+ passthrough.bgYellowBright = passthrough;
+ passthrough.bgBlueBright = passthrough;
+ passthrough.bgMagentaBright = passthrough;
+ passthrough.bgCyanBright = passthrough;
+ passthrough.bgWhiteBright = passthrough;
+ Colors = passthrough;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/data_tables.js b/testing/web-platform/mozilla/tests/webgpu/common/util/data_tables.js
new file mode 100644
index 0000000000..569af1031d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/data_tables.js
@@ -0,0 +1,129 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/
+
+export function keysOf(obj) {
+ return Object.keys(obj);
+}
+
+export function numericKeysOf(obj) {
+ return Object.keys(obj).map((n) => Number(n));
+}
+
+/**
+ * @returns a new Record from `objects`, using the string returned by Object.toString() as the keys
+ * and the objects as the values.
+ */
+export function objectsToRecord(objects) {
+ const record = {};
+ return objects.reduce((obj, type) => {
+ return {
+ ...obj,
+ [type.toString()]: type
+ };
+ }, record);
+}
+
+/**
+ * Creates an info lookup object from a more nicely-formatted table. See below for examples.
+ *
+ * Note: Using `as const` on the arguments to this function is necessary to infer the correct type.
+ */
+export function makeTable(
+
+
+
+
+members,
+defaults,
+table)
+
+
+{
+ const result = {};
+ for (const [k, v] of Object.entries(table)) {
+ const item = {};
+ for (let i = 0; i < members.length; ++i) {
+ item[members[i]] = v[i] ?? defaults[i];
+ }
+ result[k] = item;
+ }
+
+ return result;
+}
+
+/**
+ * Creates an info lookup object from a more nicely-formatted table.
+ *
+ * Note: Using `as const` on the arguments to this function is necessary to infer the correct type.
+ *
+ * Example:
+ *
+ * ```
+ * const t = makeTableWithDefaults(
+ * { c: 'default' }, // columnRenames
+ * ['a', 'default', 'd'], // columnsKept
+ * ['a', 'b', 'c', 'd'], // columns
+ * [123, 456, 789, 1011], // defaults
+ * { // table
+ * foo: [1, 2, 3, 4],
+ * bar: [5, , , 8],
+ * moo: [ , 9,10, ],
+ * }
+ * );
+ *
+ * // t = {
+ * // foo: { a: 1, default: 3, d: 4 },
+ * // bar: { a: 5, default: 789, d: 8 },
+ * // moo: { a: 123, default: 10, d: 1011 },
+ * // };
+ * ```
+ *
+ * MAINTENANCE_TODO: `ZipKeysWithValues<Members, Table[k], Defaults>` is incorrect
+ * because Members no longer maps to Table[k]. It's not clear if this is even possible to fix
+ * because it requires mapping, not zipping. Maybe passing in a index mapping
+ * would fix it (which is gross) but if you have columnsKept as [0, 2, 3] then maybe it would
+ * be possible to generate the correct type? I don't think we can generate the map at compile time
+ * so we'd have to hand code it. Other ideas, don't generate kLimitsInfoCore and kLimitsInfoCompat
+ * where they are keys of infos. Instead, generate kLimitsInfoCoreDefaults, kLimitsInfoCoreMaximums,
+ * kLimitsInfoCoreClasses where each is just a `{[k: string]: type}`. Could zip those after or,
+ * maybe that suggests passing in the hard coded indices would work.
+ *
+ * @param columnRenames the name of the column in the table that will be assigned to the 'default' property of each entry.
+ * @param columnsKept the names of properties you want in the generated lookup table. This must be a subset of the columns of the tables except for the name 'default' which is looked from the previous argument.
+ * @param columns the names of the columns of the name
+ * @param defaults the default value by column for any element in a row of the table that is undefined
+ * @param table named table rows.
+ */
+export function makeTableRenameAndFilter(
+
+
+
+
+
+columnRenames,
+columnsKept,
+columns,
+defaults,
+table)
+
+
+{
+ const result = {};
+ const keyToIndex = new Map(
+ columnsKept.map((name) => {
+ const remappedName = columnRenames[name] === undefined ? name : columnRenames[name];
+ return [name, columns.indexOf(remappedName)];
+ })
+ );
+ for (const [k, v] of Object.entries(table)) {
+ const item = {};
+ for (const member of columnsKept) {
+ const ndx = keyToIndex.get(member);
+ item[member] = v[ndx] ?? defaults[ndx];
+ }
+ result[k] = item;
+ }
+
+ return result;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/navigator_gpu.js b/testing/web-platform/mozilla/tests/webgpu/common/util/navigator_gpu.js
new file mode 100644
index 0000000000..d5db9f936e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/navigator_gpu.js
@@ -0,0 +1,86 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /// <reference types="@webgpu/types" />
+
+import { ErrorWithExtra, assert, objectEquals } from './util.js';
+
+/**
+ * Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations).
+ * Throws an exception if not found.
+ */
+function defaultGPUProvider() {
+ assert(
+ typeof navigator !== 'undefined' && navigator.gpu !== undefined,
+ 'No WebGPU implementation found'
+ );
+ return navigator.gpu;
+}
+
+/**
+ * GPUProvider is a function that creates and returns a new GPU instance.
+ * May throw an exception if a GPU cannot be created.
+ */
+
+
+let gpuProvider = defaultGPUProvider;
+
+/**
+ * Sets the function to create and return a new GPU instance.
+ */
+export function setGPUProvider(provider) {
+ assert(impl === undefined, 'setGPUProvider() should not be after getGPU()');
+ gpuProvider = provider;
+}
+
+let impl = undefined;
+
+let defaultRequestAdapterOptions;
+
+export function setDefaultRequestAdapterOptions(options) {
+ // It's okay to call this if you don't change the options
+ if (objectEquals(options, defaultRequestAdapterOptions)) {
+ return;
+ }
+ if (impl) {
+ throw new Error('must call setDefaultRequestAdapterOptions before getGPU');
+ }
+ defaultRequestAdapterOptions = { ...options };
+}
+
+export function getDefaultRequestAdapterOptions() {
+ return defaultRequestAdapterOptions;
+}
+
+/**
+ * Finds and returns the `navigator.gpu` object (or equivalent, for non-browser implementations).
+ * Throws an exception if not found.
+ */
+export function getGPU(recorder) {
+ if (impl) {
+ return impl;
+ }
+
+ impl = gpuProvider();
+
+ if (defaultRequestAdapterOptions) {
+
+ const oldFn = impl.requestAdapter;
+ impl.requestAdapter = function (
+ options)
+ {
+ const promise = oldFn.call(this, { ...defaultRequestAdapterOptions, ...options });
+ if (recorder) {
+ void promise.then(async (adapter) => {
+ if (adapter) {
+ const info = await adapter.requestAdapterInfo();
+ const infoString = `Adapter: ${info.vendor} / ${info.architecture} / ${info.device}`;
+ recorder.debug(new ErrorWithExtra(infoString, () => ({ adapterInfo: info })));
+ }
+ });
+ }
+ return promise;
+ };
+ }
+
+ return impl;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/preprocessor.js b/testing/web-platform/mozilla/tests/webgpu/common/util/preprocessor.js
new file mode 100644
index 0000000000..8783ee16e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/preprocessor.js
@@ -0,0 +1,149 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from './util.js'; // The state of the preprocessor is a stack of States.
+var
+State = /*#__PURE__*/function (State) {State[State["Seeking"] = 0] = "Seeking";State[State["Passing"] = 1] = "Passing";State[State["Skipping"] = 2] = "Skipping";return State;}(State || {});
+
+
+// Have already seen a passing condition; now skipping the rest
+
+
+// The transitions in the state space are the following preprocessor directives:
+// - Sibling elif
+// - Sibling else
+// - Sibling endif
+// - Child if
+class Directive {
+
+
+ constructor(depth) {
+ this.depth = depth;
+ }
+
+ checkDepth(stack) {
+ assert(
+ stack.length === this.depth,
+ `Number of "$"s must match nesting depth, currently ${stack.length} (e.g. $if $$if $$endif $endif)`
+ );
+ }
+
+
+}
+
+class If extends Directive {
+
+
+ constructor(depth, predicate) {
+ super(depth);
+ this.predicate = predicate;
+ }
+
+ applyTo(stack) {
+ this.checkDepth(stack);
+ const parentState = stack[stack.length - 1].state;
+ stack.push({
+ allowsFollowingElse: true,
+ state:
+ parentState !== State.Passing ?
+ State.Skipping :
+ this.predicate ?
+ State.Passing :
+ State.Seeking
+ });
+ }
+}
+
+class ElseIf extends If {
+ applyTo(stack) {
+ assert(stack.length >= 1);
+ const { allowsFollowingElse, state: siblingState } = stack.pop();
+ this.checkDepth(stack);
+ assert(allowsFollowingElse, 'pp.elif after pp.else');
+ if (siblingState !== State.Seeking) {
+ stack.push({ allowsFollowingElse: true, state: State.Skipping });
+ } else {
+ super.applyTo(stack);
+ }
+ }
+}
+
+class Else extends Directive {
+ applyTo(stack) {
+ assert(stack.length >= 1);
+ const { allowsFollowingElse, state: siblingState } = stack.pop();
+ this.checkDepth(stack);
+ assert(allowsFollowingElse, 'pp.else after pp.else');
+ stack.push({
+ allowsFollowingElse: false,
+ state: siblingState === State.Seeking ? State.Passing : State.Skipping
+ });
+ }
+}
+
+class EndIf extends Directive {
+ applyTo(stack) {
+ stack.pop();
+ this.checkDepth(stack);
+ }
+}
+
+/**
+ * A simple template-based, non-line-based preprocessor implementing if/elif/else/endif.
+ *
+ * @example
+ * ```
+ * const shader = pp`
+ * ${pp._if(expr)}
+ * const x: ${type} = ${value};
+ * ${pp._elif(expr)}
+ * ${pp.__if(expr)}
+ * ...
+ * ${pp.__else}
+ * ...
+ * ${pp.__endif}
+ * ${pp._endif}`;
+ * ```
+ *
+ * @param strings - The array of constant string chunks of the template string.
+ * @param ...values - The array of interpolated `${}` values within the template string.
+ */
+export function pp(
+strings,
+...values)
+{
+ let result = '';
+ const stateStack = [{ allowsFollowingElse: false, state: State.Passing }];
+
+ for (let i = 0; i < values.length; ++i) {
+ const passing = stateStack[stateStack.length - 1].state === State.Passing;
+ if (passing) {
+ result += strings[i];
+ }
+
+ const value = values[i];
+ if (value instanceof Directive) {
+ value.applyTo(stateStack);
+ } else {
+ if (passing) {
+ result += value;
+ }
+ }
+ }
+ assert(stateStack.length === 1, 'Unterminated preprocessor condition at end of file');
+ result += strings[values.length];
+
+ return result;
+}
+pp._if = (predicate) => new If(1, predicate);
+pp._elif = (predicate) => new ElseIf(1, predicate);
+pp._else = new Else(1);
+pp._endif = new EndIf(1);
+pp.__if = (predicate) => new If(2, predicate);
+pp.__elif = (predicate) => new ElseIf(2, predicate);
+pp.__else = new Else(2);
+pp.__endif = new EndIf(2);
+pp.___if = (predicate) => new If(3, predicate);
+pp.___elif = (predicate) => new ElseIf(3, predicate);
+pp.___else = new Else(3);
+pp.___endif = new EndIf(3);
+// Add more if needed. \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/timeout.js b/testing/web-platform/mozilla/tests/webgpu/common/util/timeout.js
new file mode 100644
index 0000000000..f70472eeee
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/timeout.js
@@ -0,0 +1,7 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /** Defined by WPT. Like `setTimeout`, but applies a timeout multiplier for slow test systems. */
+/**
+ * Equivalent of `setTimeout`, but redirects to WPT's `step_timeout` when it is defined.
+ */
+export const timeout = typeof step_timeout !== 'undefined' ? step_timeout : setTimeout; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/types.js b/testing/web-platform/mozilla/tests/webgpu/common/util/types.js
new file mode 100644
index 0000000000..0e893ab2fa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/types.js
@@ -0,0 +1,97 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /** Forces a type to resolve its type definitions, to make it readable/debuggable. */
+
+
+
+
+/** Returns the type `true` iff X and Y are exactly equal */
+
+
+
+
+
+export function assertTypeTrue() {}
+
+/** `ReadonlyArray` of `ReadonlyArray`s. */
+
+/** `ReadonlyArray` of `ReadonlyArray`s of `ReadonlyArray`s. */
+
+
+/**
+ * Deep version of the Readonly<> type, with support for tuples (up to length 7).
+ * <https://gist.github.com/masterkidan/7322752f569b1bba53e0426266768623>
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Computes the intersection of a set of types, given the union of those types.
+ *
+ * From: https://stackoverflow.com/a/56375136
+ */
+
+
+
+
+/** "Type asserts" that `X` is a subtype of `Y`. */
+
+
+
+
+
+
+/**
+ * Zips a key tuple type and a value tuple type together into an object.
+ *
+ * @template Keys Keys of the resulting object.
+ * @template Values Values of the resulting object. If a key corresponds to a `Values` member that
+ * is undefined or past the end, it defaults to the corresponding `Defaults` member.
+ * @template Defaults Default values. If a key corresponds to a `Defaults` member that is past the
+ * end, the default falls back to `undefined`.
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// K exhausted \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/util.js b/testing/web-platform/mozilla/tests/webgpu/common/util/util.js
new file mode 100644
index 0000000000..34934af6c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/util.js
@@ -0,0 +1,476 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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';
+
+/**
+ * Error with arbitrary `extra` data attached, for debugging.
+ * The extra data is omitted if not running the test in debug mode (`?debug=1`).
+ */
+export class ErrorWithExtra extends Error {
+
+
+ /**
+ * `extra` function is only called if in debug mode.
+ * If an `ErrorWithExtra` is passed, its message is used and its extras are passed through.
+ */
+
+
+ constructor(baseOrMessage, newExtra) {
+ const message = typeof baseOrMessage === 'string' ? baseOrMessage : baseOrMessage.message;
+ super(message);
+
+ const oldExtras = baseOrMessage instanceof ErrorWithExtra ? baseOrMessage.extra : {};
+ this.extra = Logger.globalDebugMode ?
+ { ...oldExtras, ...newExtra() } :
+ { omitted: 'pass ?debug=1' };
+ }
+}
+
+/**
+ * Asserts `condition` is true. Otherwise, throws an `Error` with the provided message.
+ */
+export function assert(condition, msg) {
+ if (!condition) {
+ throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
+ }
+}
+
+/** If the argument is an Error, throw it. Otherwise, pass it back. */
+export function assertOK(value) {
+ if (value instanceof Error) {
+ throw value;
+ }
+ return value;
+}
+
+/** Options for assertReject, shouldReject, and friends. */
+
+
+/**
+ * Resolves if the provided promise rejects; rejects if it does not.
+ */
+export async function assertReject(
+expectedName,
+p,
+{ allowMissingStack = false, message } = {})
+{
+ try {
+ await p;
+ unreachable(message);
+ } catch (ex) {
+ // Asserted as expected
+ if (!allowMissingStack) {
+ const m = message ? ` (${message})` : '';
+ assert(
+ ex instanceof Error && typeof ex.stack === 'string',
+ 'threw as expected, but missing stack' + m
+ );
+ }
+ }
+}
+
+/**
+ * Assert this code is unreachable. Unconditionally throws an `Error`.
+ */
+export function unreachable(msg) {
+ throw new Error(msg);
+}
+
+/**
+ * Throw a `SkipTestCase` exception, which skips the test case.
+ */
+export function skipTestCase(msg) {
+ throw new SkipTestCase(msg);
+}
+
+/**
+ * The `performance` interface.
+ * It is available in all browsers, but it is not in scope by default in Node.
+ */
+const perf = typeof performance !== 'undefined' ? performance : require('perf_hooks').performance;
+
+/**
+ * Calls the appropriate `performance.now()` depending on whether running in a browser or Node.
+ */
+export function now() {
+ return perf.now();
+}
+
+/**
+ * Returns a promise which resolves after the specified time.
+ */
+export function resolveOnTimeout(ms) {
+ return new Promise((resolve) => {
+ timeout(() => {
+ resolve();
+ }, ms);
+ });
+}
+
+export class PromiseTimeoutError extends Error {}
+
+/**
+ * Returns a promise which rejects after the specified time.
+ */
+export function rejectOnTimeout(ms, msg) {
+ return new Promise((_resolve, reject) => {
+ timeout(() => {
+ reject(new PromiseTimeoutError(msg));
+ }, ms);
+ });
+}
+
+/**
+ * Takes a promise `p`, and returns a new one which rejects if `p` takes too long,
+ * and otherwise passes the result through.
+ */
+export function raceWithRejectOnTimeout(p, ms, msg) {
+ if (globalTestConfig.noRaceWithRejectOnTimeout) {
+ return p;
+ }
+ // Setup a promise that will reject after `ms` milliseconds. We cancel this timeout when
+ // `p` is finalized, so the JavaScript VM doesn't hang around waiting for the timer to
+ // complete, once the test runner has finished executing the tests.
+ const timeoutPromise = new Promise((_resolve, reject) => {
+ const handle = timeout(() => {
+ reject(new PromiseTimeoutError(msg));
+ }, ms);
+ p = p.finally(() => clearTimeout(handle));
+ });
+ return Promise.race([p, timeoutPromise]);
+}
+
+/**
+ * Takes a promise `p` and returns a new one which rejects if `p` resolves or rejects,
+ * and otherwise resolves after the specified time.
+ */
+export function assertNotSettledWithinTime(
+p,
+ms,
+msg)
+{
+ // Rejects regardless of whether p resolves or rejects.
+ const rejectWhenSettled = p.then(() => Promise.reject(new Error(msg)));
+ // Resolves after `ms` milliseconds.
+ const timeoutPromise = new Promise((resolve) => {
+ const handle = timeout(() => {
+ resolve(undefined);
+ }, ms);
+ void p.finally(() => clearTimeout(handle));
+ });
+ return Promise.race([rejectWhenSettled, timeoutPromise]);
+}
+
+/**
+ * Returns a `Promise.reject()`, but also registers a dummy `.catch()` handler so it doesn't count
+ * as an uncaught promise rejection in the runtime.
+ */
+export function rejectWithoutUncaught(err) {
+ const p = Promise.reject(err);
+ // Suppress uncaught promise rejection.
+ p.catch(() => {});
+ return p;
+}
+
+/**
+ * Returns true if v is a plain JavaScript object.
+ */
+export function isPlainObject(v) {
+ return !!v && Object.getPrototypeOf(v).constructor === Object.prototype.constructor;
+}
+
+/**
+ * Makes a copy of a JS `object`, with the keys reordered into sorted order.
+ */
+export function sortObjectByKey(v) {
+ const sortedObject = {};
+ for (const k of Object.keys(v).sort()) {
+ sortedObject[k] = v[k];
+ }
+ return sortedObject;
+}
+
+/**
+ * Determines whether two JS values are equal, recursing into objects and arrays.
+ * NaN is treated specially, such that `objectEquals(NaN, NaN)`. +/-0.0 are treated as equal
+ * by default, but can be opted to be distinguished.
+ * @param x the first JS values that get compared
+ * @param y the second JS values that get compared
+ * @param distinguishSignedZero if set to true, treat 0.0 and -0.0 as unequal. Default to false.
+ */
+export function objectEquals(
+x,
+y,
+distinguishSignedZero = false)
+{
+ if (typeof x !== 'object' || typeof y !== 'object') {
+ if (typeof x === 'number' && typeof y === 'number' && Number.isNaN(x) && Number.isNaN(y)) {
+ return true;
+ }
+ // Object.is(0.0, -0.0) is false while (0.0 === -0.0) is true. Other than +/-0.0 and NaN cases,
+ // Object.is works in the same way as ===.
+ return distinguishSignedZero ? Object.is(x, y) : x === y;
+ }
+ if (x === null || y === null) return x === y;
+ if (x.constructor !== y.constructor) return false;
+ if (x instanceof Function) return x === y;
+ if (x instanceof RegExp) return x === y;
+ if (x === y || x.valueOf() === y.valueOf()) return true;
+ if (Array.isArray(x) && Array.isArray(y) && x.length !== y.length) return false;
+ if (x instanceof Date) return false;
+ if (!(x instanceof Object)) return false;
+ if (!(y instanceof Object)) return false;
+
+ const x1 = x;
+ const y1 = y;
+ const p = Object.keys(x);
+ return Object.keys(y).every((i) => p.indexOf(i) !== -1) && p.every((i) => objectEquals(x1[i], y1[i]));
+}
+
+/**
+ * Generates a range of values `fn(0)..fn(n-1)`.
+ */
+export function range(n, fn) {
+ return [...new Array(n)].map((_, i) => fn(i));
+}
+
+/**
+ * Generates a range of values `fn(0)..fn(n-1)`.
+ */
+export function* iterRange(n, fn) {
+ for (let i = 0; i < n; ++i) {
+ yield fn(i);
+ }
+}
+
+/** Creates a (reusable) iterable object that maps `f` over `xs`, lazily. */
+export function mapLazy(xs, f) {
+ return {
+ *[Symbol.iterator]() {
+ for (const x of xs) {
+ yield f(x);
+ }
+ }
+ };
+}
+
+const ReorderOrders = {
+ forward: true,
+ backward: true,
+ shiftByHalf: true
+};
+
+export const kReorderOrderKeys = keysOf(ReorderOrders);
+
+/**
+ * Creates a new array from the given array with the first half
+ * swapped with the last half.
+ */
+export function shiftByHalf(arr) {
+ const len = arr.length;
+ const half = len / 2 | 0;
+ const firstHalf = arr.splice(0, half);
+ return [...arr, ...firstHalf];
+}
+
+/**
+ * Creates a reordered array from the input array based on the Order
+ */
+export function reorder(order, arr) {
+ switch (order) {
+ case 'forward':
+ return arr.slice();
+ case 'backward':
+ return arr.slice().reverse();
+ case 'shiftByHalf':{
+ // should this be pseudo random?
+ return shiftByHalf(arr);
+ }
+ }
+}
+
+const TypedArrayBufferViewInstances = [
+new Uint8Array(),
+new Uint8ClampedArray(),
+new Uint16Array(),
+new Uint32Array(),
+new Int8Array(),
+new Int16Array(),
+new Int32Array(),
+new Float16Array(),
+new Float32Array(),
+new Float64Array()];
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+export const kTypedArrayBufferViews =
+
+{
+ ...(() => {
+
+ const result = {};
+ for (const v of TypedArrayBufferViewInstances) {
+ result[v.constructor.name] = v.constructor;
+ }
+ return result;
+ })()
+};
+export const kTypedArrayBufferViewKeys = keysOf(kTypedArrayBufferViews);
+export const kTypedArrayBufferViewConstructors = Object.values(kTypedArrayBufferViews);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Creates a case parameter for a typedarray.
+ *
+ * You can't put typedarrays in case parameters directly so instead of
+ *
+ * ```
+ * u.combine('data', [
+ * new Uint8Array([1, 2, 3]),
+ * new Float32Array([4, 5, 6]),
+ * ])
+ * ```
+ *
+ * You can use
+ *
+ * ```
+ * u.combine('data', [
+ * typedArrayParam('Uint8Array' [1, 2, 3]),
+ * typedArrayParam('Float32Array' [4, 5, 6]),
+ * ])
+ * ```
+ *
+ * and then convert the params to typedarrays eg.
+ *
+ * ```
+ * .fn(t => {
+ * const data = t.params.data.map(v => typedArrayFromParam(v));
+ * })
+ * ```
+ */
+export function typedArrayParam(
+type,
+data)
+{
+ return { type, data };
+}
+
+export function createTypedArray(
+type,
+data)
+{
+ return new kTypedArrayBufferViews[type](data);
+}
+
+/**
+ * Converts a TypedArrayParam to a typedarray. See typedArrayParam
+ */
+export function typedArrayFromParam(
+param)
+{
+ const { type, data } = param;
+ return createTypedArray(type, data);
+}
+
+function subarrayAsU8(
+buf,
+{ start = 0, length })
+{
+ if (buf instanceof ArrayBuffer) {
+ return new Uint8Array(buf, start, length);
+ } else if (buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) {
+ // Don't wrap in new views if we don't need to.
+ if (start === 0 && (length === undefined || length === buf.byteLength)) {
+ return buf;
+ }
+ }
+ const byteOffset = buf.byteOffset + start * buf.BYTES_PER_ELEMENT;
+ const byteLength =
+ length !== undefined ?
+ length * buf.BYTES_PER_ELEMENT :
+ buf.byteLength - (byteOffset - buf.byteOffset);
+ return new Uint8Array(buf.buffer, byteOffset, byteLength);
+}
+
+/**
+ * Copy a range of bytes from one ArrayBuffer or TypedArray to another.
+ *
+ * `start`/`length` are in elements (or in bytes, if ArrayBuffer).
+ */
+export function memcpy(
+src,
+dst)
+{
+ subarrayAsU8(dst.dst, dst).set(subarrayAsU8(src.src, src));
+}
+
+/**
+ * Used to create a value that is specified by multiplying some runtime value
+ * by a constant and then adding a constant to it.
+ */
+
+
+
+
+
+/**
+ * Filters out SpecValues that are the same.
+ */
+export function filterUniqueValueTestVariants(valueTestVariants) {
+ return new Map(
+ valueTestVariants.map((v) => [`m:${v.mult},a:${v.add}`, v])
+ ).values();
+}
+
+/**
+ * Used to create a value that is specified by multiplied some runtime value
+ * by a constant and then adding a constant to it. This happens often in test
+ * with limits that can only be known at runtime and yet we need a way to
+ * add parameters to a test and those parameters must be constants.
+ */
+export function makeValueTestVariant(base, variant) {
+ return base * variant.mult + variant.add;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/util/wpt_reftest_wait.js b/testing/web-platform/mozilla/tests/webgpu/common/util/wpt_reftest_wait.js
new file mode 100644
index 0000000000..89b7fd1a9c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/common/util/wpt_reftest_wait.js
@@ -0,0 +1,24 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { timeout } from './timeout.js'; // Copied from https://github.com/web-platform-tests/wpt/blob/master/common/reftest-wait.js
+
+/**
+ * Remove the `reftest-wait` class on the document element.
+ * The reftest runner will wait with taking a screenshot while
+ * this class is present.
+ *
+ * See https://web-platform-tests.org/writing-tests/reftests.html#controlling-when-comparison-occurs
+ */
+export function takeScreenshot() {
+ document.documentElement.classList.remove('reftest-wait');
+}
+
+/**
+ * Call `takeScreenshot()` after a delay of at least `ms` milliseconds.
+ * @param {number} ms - milliseconds
+ */
+export function takeScreenshotDelayed(ms) {
+ timeout(() => {
+ takeScreenshot();
+ }, ms);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html
new file mode 100644
index 0000000000..7eea796523
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapter/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestAdapter:requestAdapter:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestAdapter:requestAdapter_no_parameters:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapterInfo/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapterInfo/cts.https.html
new file mode 100644
index 0000000000..5bee1a5887
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestAdapterInfo/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestAdapterInfo:adapter_info:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestAdapterInfo:adapter_info_with_hints:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestDevice/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestDevice/cts.https.html
new file mode 100644
index 0000000000..edd19233c4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/adapter/requestDevice/cts.https.html
@@ -0,0 +1,44 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:default:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:features,known:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:features,unknown:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:invalid:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:limit,better_than_supported:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:limit,worse_than_default:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:limits,supported:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:limits,unknown:*'>
+<meta name=variant content='?q=webgpu:api,operation,adapter,requestDevice:stale:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map/cts.https.html
new file mode 100644
index 0000000000..552367a75a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mapAsync,mapState:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mapAsync,read,typedArrayAccess:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mapAsync,read:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mapAsync,write,unchanged_ranges_preserved:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mapAsync,write:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mappedAtCreation,mapState:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,map:remapped_for_write:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_ArrayBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_ArrayBuffer/cts.https.html
new file mode 100644
index 0000000000..2ae75fc5b4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_ArrayBuffer/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,buffers,map_ArrayBuffer:postMessage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_detach/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_detach/cts.https.html
new file mode 100644
index 0000000000..426948dab3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_detach/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,buffers,map_detach:while_mapped:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_oom/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_oom/cts.https.html
new file mode 100644
index 0000000000..ebaa6b1d9c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/map_oom/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,buffers,map_oom:mappedAtCreation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/threading/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/threading/cts.https.html
new file mode 100644
index 0000000000..585f2a87de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/buffers/threading/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,buffers,threading:destroyed:*'>
+<meta name=variant content='?q=webgpu:api,operation,buffers,threading:serialize:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/basic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/basic/cts.https.html
new file mode 100644
index 0000000000..67d8bcc545
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/basic/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:b2t2b:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:b2t2t2b:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:empty:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/clearBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/clearBuffer/cts.https.html
new file mode 100644
index 0000000000..aeb90bda3f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/clearBuffer/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,clearBuffer:clear:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyBufferToBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyBufferToBuffer/cts.https.html
new file mode 100644
index 0000000000..84f067e313
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyBufferToBuffer/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyBufferToBuffer:copy_order:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyBufferToBuffer:single:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyBufferToBuffer:state_transitions:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyTextureToTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyTextureToTexture/cts.https.html
new file mode 100644
index 0000000000..ae5e5bd739
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/copyTextureToTexture/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,compressed,array:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,compressed,non_array:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,non_compressed,array:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:color_textures,non_compressed,non_array:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:copy_depth_stencil:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:copy_multisampled_color:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:copy_multisampled_depth:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,copyTextureToTexture:zero_sized:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/image_copy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/image_copy/cts.https.html
new file mode 100644
index 0000000000..4c10b641ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/image_copy/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:mip_levels:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes_copy_depth_stencil:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:origins_and_extents:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:rowsPerImage_and_bytesPerRow_depth_stencil:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,image_copy:undefined_params:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/programmable/state_tracking/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/programmable/state_tracking/cts.https.html
new file mode 100644
index 0000000000..5a1265a40b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/programmable/state_tracking/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_before_pipeline:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_indices:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_multiple_sets:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,programmable,state_tracking:bind_group_order:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,programmable,state_tracking:compatible_pipelines:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,programmable,state_tracking:one_bind_group_multiple_slots:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/queries/occlusionQuery/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/queries/occlusionQuery/cts.https.html
new file mode 100644
index 0000000000..2396d8ab1a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/queries/occlusionQuery/cts.https.html
@@ -0,0 +1,44 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,alpha_to_coverage:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,basic:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,depth:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,empty:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,initial:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,multi_resolve:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,sample_mask:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,scissor:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,queries,occlusionQuery:occlusion_query,stencil:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/render/state_tracking/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/render/state_tracking/cts.https.html
new file mode 100644
index 0000000000..01f07e4460
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/command_buffer/render/state_tracking/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,state_tracking:change_pipeline_before_and_after_vertex_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,state_tracking:set_index_buffer_before_non_indexed_draw:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,state_tracking:set_index_buffer_without_changing_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,state_tracking:set_vertex_buffer_but_not_used_in_draw:*'>
+<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,state_tracking:set_vertex_buffer_without_changing_buffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute/basic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute/basic/cts.https.html
new file mode 100644
index 0000000000..6298523c25
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute/basic/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,compute,basic:large_dispatch:*'>
+<meta name=variant content='?q=webgpu:api,operation,compute,basic:memcpy:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute_pipeline/overrides/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute_pipeline/overrides/cts.https.html
new file mode 100644
index 0000000000..d666b0f651
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/compute_pipeline/overrides/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,compute_pipeline,overrides:basic:*'>
+<meta name=variant content='?q=webgpu:api,operation,compute_pipeline,overrides:multi_entry_points:*'>
+<meta name=variant content='?q=webgpu:api,operation,compute_pipeline,overrides:numeric_id:*'>
+<meta name=variant content='?q=webgpu:api,operation,compute_pipeline,overrides:precision:*'>
+<meta name=variant content='?q=webgpu:api,operation,compute_pipeline,overrides:shared_shader_module:*'>
+<meta name=variant content='?q=webgpu:api,operation,compute_pipeline,overrides:workgroup_size:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/device/lost/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/device/lost/cts.https.html
new file mode 100644
index 0000000000..1dff974133
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/device/lost/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,device,lost:lost_on_destroy:*'>
+<meta name=variant content='?q=webgpu:api,operation,device,lost:not_lost_on_gc:*'>
+<meta name=variant content='?q=webgpu:api,operation,device,lost:same_object:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/labels/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/labels/cts.https.html
new file mode 100644
index 0000000000..17da303100
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/labels/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,labels:object_has_descriptor_label:*'>
+<meta name=variant content='?q=webgpu:api,operation,labels:wrappers_do_not_share_labels:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/multiple_buffers/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/multiple_buffers/cts.https.html
new file mode 100644
index 0000000000..1308e5c5c3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/multiple_buffers/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,multiple_buffers:multiple_pairs_of_dispatches_in_one_compute_pass:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,multiple_buffers:multiple_pairs_of_draws_in_one_render_bundle:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,multiple_buffers:multiple_pairs_of_draws_in_one_render_pass:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,multiple_buffers:rw:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,multiple_buffers:wr:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,multiple_buffers:ww:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/single_buffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/single_buffer/cts.https.html
new file mode 100644
index 0000000000..99b890d00e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/buffer/single_buffer/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,single_buffer:rw:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,single_buffer:two_dispatches_in_the_same_compute_pass:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,single_buffer:two_draws_in_the_same_render_bundle:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,single_buffer:two_draws_in_the_same_render_pass:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,single_buffer:wr:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,buffer,single_buffer:ww:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/texture/same_subresource/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/texture/same_subresource/cts.https.html
new file mode 100644
index 0000000000..160084ee41
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/memory_sync/texture/same_subresource/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,texture,same_subresource:rw,single_pass,load_resolve:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,texture,same_subresource:rw,single_pass,load_store:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,texture,same_subresource:rw:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,texture,same_subresource:wr:*'>
+<meta name=variant content='?q=webgpu:api,operation,memory_sync,texture,same_subresource:ww:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/onSubmittedWorkDone/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/onSubmittedWorkDone/cts.https.html
new file mode 100644
index 0000000000..6a39dc5c69
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/onSubmittedWorkDone/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,onSubmittedWorkDone:many,parallel:*'>
+<meta name=variant content='?q=webgpu:api,operation,onSubmittedWorkDone:many,parallel_order:*'>
+<meta name=variant content='?q=webgpu:api,operation,onSubmittedWorkDone:many,serial:*'>
+<meta name=variant content='?q=webgpu:api,operation,onSubmittedWorkDone:with_work:*'>
+<meta name=variant content='?q=webgpu:api,operation,onSubmittedWorkDone:without_work:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/pipeline/default_layout/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/pipeline/default_layout/cts.https.html
new file mode 100644
index 0000000000..495fb8a971
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/pipeline/default_layout/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,pipeline,default_layout:getBindGroupLayout_js_object:*'>
+<meta name=variant content='?q=webgpu:api,operation,pipeline,default_layout:incompatible_with_explicit:*'>
+<meta name=variant content='?q=webgpu:api,operation,pipeline,default_layout:layout:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/queue/writeBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/queue/writeBuffer/cts.https.html
new file mode 100644
index 0000000000..9a1b5b771a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/queue/writeBuffer/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,queue,writeBuffer:array_types:*'>
+<meta name=variant content='?q=webgpu:api,operation,queue,writeBuffer:multiple_writes_at_different_offsets_and_sizes:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/reflection/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/reflection/cts.https.html
new file mode 100644
index 0000000000..e12ab28926
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/reflection/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,reflection:buffer_reflection_attributes:*'>
+<meta name=variant content='?q=webgpu:api,operation,reflection:query_set_reflection_attributes:*'>
+<meta name=variant content='?q=webgpu:api,operation,reflection:texture_reflection_attributes:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/clear_value/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/clear_value/cts.https.html
new file mode 100644
index 0000000000..cfc0a8412f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/clear_value/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pass,clear_value:layout:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,clear_value:loaded:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,clear_value:srgb:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,clear_value:stencil_clear_value:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,clear_value:stored:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/resolve/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/resolve/cts.https.html
new file mode 100644
index 0000000000..2756795af4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/resolve/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pass,resolve:render_pass_resolve:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeOp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeOp/cts.https.html
new file mode 100644
index 0000000000..3b1c062aa5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeOp/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_only:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_with_depth_stencil_attachment:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:render_pass_store_op,depth_stencil_attachment_only:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:render_pass_store_op,multiple_color_attachments:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeop2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeop2/cts.https.html
new file mode 100644
index 0000000000..de53d7b770
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pass/storeop2/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pass,storeop2:storeOp_controls_whether_1x1_drawn_quad_is_stored:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/culling_tests/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/culling_tests/cts.https.html
new file mode 100644
index 0000000000..2e822f7432
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/culling_tests/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,culling_tests:culling:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/overrides/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/overrides/cts.https.html
new file mode 100644
index 0000000000..4c9a468737
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/overrides/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,overrides:basic:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,overrides:multi_entry_points:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,overrides:precision:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,overrides:shared_shader_module:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/pipeline_output_targets/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/pipeline_output_targets/cts.https.html
new file mode 100644
index 0000000000..f885316fb4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/pipeline_output_targets/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,pipeline_output_targets:color,attachments:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,pipeline_output_targets:color,component_count,blend:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,pipeline_output_targets:color,component_count:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/primitive_topology/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/primitive_topology/cts.https.html
new file mode 100644
index 0000000000..647bd643ee
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/primitive_topology/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,primitive_topology:basic:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,primitive_topology:unaligned_vertex_count:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/sample_mask/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/sample_mask/cts.https.html
new file mode 100644
index 0000000000..c3c8fc6e3c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/sample_mask/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,sample_mask:alpha_to_coverage_mask:*'>
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,sample_mask:fragment_output_mask:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline/cts.https.html
new file mode 100644
index 0000000000..9911ce8929
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,render_pipeline,vertex_only_render_pipeline:draw_depth_and_stencil_with_vertex_only_pipeline:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/basic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/basic/cts.https.html
new file mode 100644
index 0000000000..823d8b9399
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/basic/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,basic:clear:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,basic:fullscreen_quad:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,basic:large_draw:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/color_target_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/color_target_state/cts.https.html
new file mode 100644
index 0000000000..eb6ca82c6e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/color_target_state/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:blend_constant,initial:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:blend_constant,not_inherited:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:blend_constant,setting:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:blending,GPUBlendComponent:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:blending,clamping:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:blending,formats:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:color_write_mask,blending_disabled:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,color_target_state:color_write_mask,channel_work:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth/cts.https.html
new file mode 100644
index 0000000000..3e4e3bb6c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth:depth_compare_func:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth:depth_disabled:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth:depth_test_fail:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth:depth_write_disabled:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth:reverse_depth:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_bias/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_bias/cts.https.html
new file mode 100644
index 0000000000..84ae4ea23f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_bias/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth_bias:depth_bias:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth_bias:depth_bias_24bit_format:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_clip_clamp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_clip_clamp/cts.https.html
new file mode 100644
index 0000000000..88ef2fe1b3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/depth_clip_clamp/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth_clip_clamp:depth_clamp_and_clip:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,depth_clip_clamp:depth_test_input_clamped:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/draw/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/draw/cts.https.html
new file mode 100644
index 0000000000..051f9878da
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/draw/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,draw:arguments:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,draw:default_arguments:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,draw:largeish_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,draw:vertex_attributes,basic:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,draw:vertex_attributes,formats:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/indirect_draw/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/indirect_draw/cts.https.html
new file mode 100644
index 0000000000..634e2fc556
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/indirect_draw/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,indirect_draw:basics:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/stencil/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/stencil/cts.https.html
new file mode 100644
index 0000000000..4df360d1d1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/rendering/stencil/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,rendering,stencil:stencil_compare_func:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,stencil:stencil_depthFailOp_operation:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,stencil:stencil_failOp_operation:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,stencil:stencil_passOp_operation:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,stencil:stencil_read_write_mask:*'>
+<meta name=variant content='?q=webgpu:api,operation,rendering,stencil:stencil_reference_initialized:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/buffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/buffer/cts.https.html
new file mode 100644
index 0000000000..e3d40f4c54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/buffer/cts.https.html
@@ -0,0 +1,51 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:copy_buffer_to_buffer_copy_source:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:copy_buffer_to_texture:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:copy_texture_to_partial_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:index_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:indirect_buffer_for_dispatch_indirect:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:indirect_buffer_for_draw_indirect:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:map_partial_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:map_whole_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:mapped_at_creation_partial_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:mapped_at_creation_whole_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:partial_write_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:readonly_storage_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:resolve_query_set_to_partial_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:storage_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:uniform_buffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,resource_init,buffer:vertex_buffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/texture_zero/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/texture_zero/cts.https.html
new file mode 100644
index 0000000000..8f18f51064
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/resource_init/texture_zero/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,resource_init,texture_zero:uninitialized_texture_is_zero:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/anisotropy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/anisotropy/cts.https.html
new file mode 100644
index 0000000000..20d75e672a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/anisotropy/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,sampling,anisotropy:anisotropic_filter_checkerboard:*'>
+<meta name=variant content='?q=webgpu:api,operation,sampling,anisotropy:anisotropic_filter_mipmap_color:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/filter_mode/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/filter_mode/cts.https.html
new file mode 100644
index 0000000000..ea5ec47103
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/sampling/filter_mode/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,sampling,filter_mode:magFilter,linear:*'>
+<meta name=variant content='?q=webgpu:api,operation,sampling,filter_mode:magFilter,nearest:*'>
+<meta name=variant content='?q=webgpu:api,operation,sampling,filter_mode:minFilter,linear:*'>
+<meta name=variant content='?q=webgpu:api,operation,sampling,filter_mode:minFilter,nearest:*'>
+<meta name=variant content='?q=webgpu:api,operation,sampling,filter_mode:mipmapFilter:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/shader_module/compilation_info/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/shader_module/compilation_info/cts.https.html
new file mode 100644
index 0000000000..e5669d82a5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/shader_module/compilation_info/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,shader_module,compilation_info:getCompilationInfo_returns:*'>
+<meta name=variant content='?q=webgpu:api,operation,shader_module,compilation_info:line_number_and_position:*'>
+<meta name=variant content='?q=webgpu:api,operation,shader_module,compilation_info:offset_and_length:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/format_reinterpretation/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/format_reinterpretation/cts.https.html
new file mode 100644
index 0000000000..d16a346f20
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/format_reinterpretation/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,texture_view,format_reinterpretation:render_and_resolve_attachment:*'>
+<meta name=variant content='?q=webgpu:api,operation,texture_view,format_reinterpretation:texture_binding:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/read/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/read/cts.https.html
new file mode 100644
index 0000000000..c691ceaf71
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/read/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,texture_view,read:aspect:*'>
+<meta name=variant content='?q=webgpu:api,operation,texture_view,read:dimension:*'>
+<meta name=variant content='?q=webgpu:api,operation,texture_view,read:format:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/write/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/write/cts.https.html
new file mode 100644
index 0000000000..ed04e53c7f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/texture_view/write/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,texture_view,write:aspect:*'>
+<meta name=variant content='?q=webgpu:api,operation,texture_view,write:dimension:*'>
+<meta name=variant content='?q=webgpu:api,operation,texture_view,write:format:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/uncapturederror/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/uncapturederror/cts.https.html
new file mode 100644
index 0000000000..2172a801a6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/uncapturederror/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,uncapturederror:constructor:*'>
+<meta name=variant content='?q=webgpu:api,operation,uncapturederror:iff_uncaptured:*'>
+<meta name=variant content='?q=webgpu:api,operation,uncapturederror:only_original_device_is_event_target:*'>
+<meta name=variant content='?q=webgpu:api,operation,uncapturederror:uncapturederror_from_non_originating_thread:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/correctness/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/correctness/cts.https.html
new file mode 100644
index 0000000000..59fe20a98b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/correctness/cts.https.html
@@ -0,0 +1,45 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:array_stride_zero:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:buffers_with_varying_step_mode:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:discontiguous_location_and_attribs:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:max_buffers_and_attribs:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:non_zero_array_stride_and_attribute_offset:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:overlapping_attributes:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:setVertexBuffer_offset_and_attribute_offset:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_interleaved:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_overlapped:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,correctness:vertex_format_to_shader_format_conversion:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/index_format/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/index_format/cts.https.html
new file mode 100644
index 0000000000..86cffeb397
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/operation/vertex_state/index_format/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,index_format:index_format,change_pipeline_after_setIndexBuffer:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,index_format:index_format,setIndexBuffer_before_setPipeline:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,index_format:index_format,setIndexBuffer_different_formats:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,index_format:index_format,uint16:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,index_format:index_format,uint32:*'>
+<meta name=variant content='?q=webgpu:api,operation,vertex_state,index_format:primitive_restart:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/create/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/create/cts.https.html
new file mode 100644
index 0000000000..57498c3ba0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/create/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,buffer,create:createBuffer_invalid_and_oom:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,create:limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,create:size:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,create:usage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/destroy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/destroy/cts.https.html
new file mode 100644
index 0000000000..c2ef52036a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/destroy/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,buffer,destroy:all_usages:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,destroy:error_buffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,destroy:twice:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,destroy:while_mapped:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/mapping/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/mapping/cts.https.html
new file mode 100644
index 0000000000..5af3380447
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/buffer/mapping/cts.https.html
@@ -0,0 +1,68 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:gc_behavior,mapAsync:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:gc_behavior,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,disjoinRanges_many:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,disjointRanges:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,offsetAndSizeAlignment,mapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,offsetAndSizeAlignment,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,sizeAndOffsetOOB,mapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,sizeAndOffsetOOB,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,destroyed:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,invalid_mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,mapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,mappedAgain:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,mappingPending:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,state,unmapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,subrange,mapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:getMappedRange,subrange,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,abort_over_invalid_error:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,earlyRejection:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,invalidBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,offsetAndSizeAlignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,offsetAndSizeOOB:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,sizeUnspecifiedOOB:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,state,destroyed:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,state,mapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,state,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,state,mappingPending:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:mapAsync,usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:unmap,state,destroyed:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:unmap,state,mapped:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:unmap,state,mappedAtCreation:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:unmap,state,mappingPending:*'>
+<meta name=variant content='?q=webgpu:api,validation,buffer,mapping:unmap,state,unmapped:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/query_types/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/query_types/cts.https.html
new file mode 100644
index 0000000000..4d5639e434
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/query_types/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,query_types:createQuerySet:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,query_types:writeTimestamp:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/texture_formats/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/texture_formats/cts.https.html
new file mode 100644
index 0000000000..75c18d61af
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/features/texture_formats/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:canvas_configuration:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:canvas_configuration_view_formats:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:check_capability_guarantees:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:depth_stencil_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:render_bundle_encoder_descriptor_depth_stencil_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor_view_formats:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,features,texture_formats:texture_view_descriptor:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindGroups/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindGroups/cts.https.html
new file mode 100644
index 0000000000..c5cbd01b9b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindGroups/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindGroups:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindGroups:createPipelineLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindGroups:validate,maxBindGroupsPlusVertexBuffers:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup/cts.https.html
new file mode 100644
index 0000000000..a5ac7ec7e1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createBindGroupLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBindingsPerBindGroup:validate:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBufferSize/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBufferSize/cts.https.html
new file mode 100644
index 0000000000..a9d79be4da
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxBufferSize/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxBufferSize:createBuffer,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample/cts.https.html
new file mode 100644
index 0000000000..a34a4fc75e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:beginRenderPass,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:createRenderBundle,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachmentBytesPerSample:createRenderPipeline,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachments/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachments/cts.https.html
new file mode 100644
index 0000000000..458868bff3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxColorAttachments/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachments:beginRenderPass,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachments:createRenderBundle,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachments:createRenderPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachments:validate,kMaxColorAttachmentsToTest:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxColorAttachments:validate,maxColorAttachmentBytesPerSample:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup/cts.https.html
new file mode 100644
index 0000000000..2d0b32b3a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeInvocationsPerWorkgroup:createComputePipeline,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX/cts.https.html
new file mode 100644
index 0000000000..6d72c348a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeX:createComputePipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeX:validate,maxComputeInvocationsPerWorkgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY/cts.https.html
new file mode 100644
index 0000000000..b3c758e3cc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeY:createComputePipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeY:validate,maxComputeInvocationsPerWorkgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ/cts.https.html
new file mode 100644
index 0000000000..bb539faf44
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeZ:createComputePipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupSizeZ:validate,maxComputeInvocationsPerWorkgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize/cts.https.html
new file mode 100644
index 0000000000..a2ae9de377
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupStorageSize:createComputePipeline,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension/cts.https.html
new file mode 100644
index 0000000000..9d66d70fca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupsPerDimension:dispatchWorkgroups,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxComputeWorkgroupsPerDimension:validate:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout/cts.https.html
new file mode 100644
index 0000000000..fab993e195
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxDynamicStorageBuffersPerPipelineLayout:createBindGroupLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout/cts.https.html
new file mode 100644
index 0000000000..3365a4b6b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxDynamicUniformBuffersPerPipelineLayout:createBindGroupLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents/cts.https.html
new file mode 100644
index 0000000000..5a047cad25
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxInterStageShaderComponents:createRenderPipeline,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables/cts.https.html
new file mode 100644
index 0000000000..8f5c4551cd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage/cts.https.html
new file mode 100644
index 0000000000..8ca321adf6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxSampledTexturesPerShaderStage:createBindGroupLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxSampledTexturesPerShaderStage:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxSampledTexturesPerShaderStage:createPipelineLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage/cts.https.html
new file mode 100644
index 0000000000..fd38679c83
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxSamplersPerShaderStage:createBindGroupLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxSamplersPerShaderStage:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxSamplersPerShaderStage:createPipelineLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize/cts.https.html
new file mode 100644
index 0000000000..185837f655
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageBufferBindingSize:createBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageBufferBindingSize:validate,maxBufferSize:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageBufferBindingSize:validate:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage/cts.https.html
new file mode 100644
index 0000000000..a1a5ce4e39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createBindGroupLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createPipelineLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage/cts.https.html
new file mode 100644
index 0000000000..a7a393f873
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageTexturesPerShaderStage:createBindGroupLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageTexturesPerShaderStage:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxStorageTexturesPerShaderStage:createPipelineLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers/cts.https.html
new file mode 100644
index 0000000000..9604b9777b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxTextureArrayLayers:createTexture,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D/cts.https.html
new file mode 100644
index 0000000000..700bdccf39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension1D:createTexture,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D/cts.https.html
new file mode 100644
index 0000000000..7eb697cd09
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:configure,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:createTexture,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:getCurrentTexture,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D/cts.https.html
new file mode 100644
index 0000000000..c82156027e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension3D:createTexture,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize/cts.https.html
new file mode 100644
index 0000000000..a8c6383f19
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxUniformBufferBindingSize:createBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxUniformBufferBindingSize:validate,maxBufferSize:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage/cts.https.html
new file mode 100644
index 0000000000..fadb5118b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxUniformBuffersPerShaderStage:createBindGroupLayout,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxUniformBuffersPerShaderStage:createPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxUniformBuffersPerShaderStage:createPipelineLayout,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexAttributes/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexAttributes/cts.https.html
new file mode 100644
index 0000000000..389dbd3ce1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexAttributes/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxVertexAttributes:createRenderPipeline,at_over:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride/cts.https.html
new file mode 100644
index 0000000000..f9d8aeba50
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxVertexBufferArrayStride:createRenderPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxVertexBufferArrayStride:validate:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBuffers/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBuffers/cts.https.html
new file mode 100644
index 0000000000..339b8c7d23
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/maxVertexBuffers/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxVertexBuffers:createRenderPipeline,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxVertexBuffers:setVertexBuffer,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,maxVertexBuffers:validate,maxBindGroupsPlusVertexBuffers:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment/cts.https.html
new file mode 100644
index 0000000000..038187ae41
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:createBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:setBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:validate,greaterThanOrEqualTo32:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minStorageBufferOffsetAlignment:validate,powerOf2:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment/cts.https.html
new file mode 100644
index 0000000000..c65107d976
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:createBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:setBindGroup,at_over:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:validate,greaterThanOrEqualTo32:*'>
+<meta name=variant content='?q=webgpu:api,validation,capability_checks,limits,minUniformBufferOffsetAlignment:validate,powerOf2:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/compute_pipeline/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/compute_pipeline/cts.https.html
new file mode 100644
index 0000000000..29d3d1966c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/compute_pipeline/cts.https.html
@@ -0,0 +1,51 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:basic:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:limits,invocations_per_workgroup,each_component:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:limits,invocations_per_workgroup:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:limits,workgroup_storage_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,identifier:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,uninitialized:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,value,type_error:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,value,validation_error,f16:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,value,validation_error:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits,workgroup_storage_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,workgroup_size,limits:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:overrides,workgroup_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:pipeline_layout,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:shader_module,compute:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:shader_module,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,compute_pipeline:shader_module,invalid:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroup/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroup/cts.https.html
new file mode 100644
index 0000000000..97505d0afe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroup/cts.https.html
@@ -0,0 +1,57 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:bind_group_layout,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:binding_count_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:binding_must_be_present_in_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:binding_must_contain_resource_defined_in_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:binding_resources,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer,effective_buffer_binding_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer,resource_binding_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer,resource_offset:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer,resource_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer,usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:minBindingSize:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:multisampled_validation:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:sampler,compare_function_with_binding_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:sampler,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:storage_texture,format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:storage_texture,mip_level_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:storage_texture,usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:texture,resource_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:texture_binding_must_have_correct_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:texture_must_have_correct_component_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroup:texture_must_have_correct_dimension:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroupLayout/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroupLayout/cts.https.html
new file mode 100644
index 0000000000..191ff2dd4f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createBindGroupLayout/cts.https.html
@@ -0,0 +1,46 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:duplicate_bindings:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_dynamic_buffers:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_bind_group_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_pipeline_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:maximum_binding_limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:multisampled_validation:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:storage_texture,formats:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:storage_texture,layout_dimension:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:visibility,VERTEX_shader_stage_buffer_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:visibility,VERTEX_shader_stage_storage_texture_access:*'>
+<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:visibility:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createPipelineLayout/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createPipelineLayout/cts.https.html
new file mode 100644
index 0000000000..092a112fb4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createPipelineLayout/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:bind_group_layouts,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:number_of_bind_group_layouts_exceeds_the_maximum_value:*'>
+<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:number_of_dynamic_buffers_exceeds_the_maximum_value:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createSampler/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createSampler/cts.https.html
new file mode 100644
index 0000000000..fff3219015
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createSampler/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,createSampler:lodMinAndMaxClamp:*'>
+<meta name=variant content='?q=webgpu:api,validation,createSampler:maxAnisotropy:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createTexture/cts.https.html
new file mode 100644
index 0000000000..04cbc6754b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createTexture/cts.https.html
@@ -0,0 +1,52 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,createTexture:dimension_type_and_format_compatibility:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:mipLevelCount,bound_check,bigger_than_integer_bit_width:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:mipLevelCount,bound_check:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:mipLevelCount,format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:sampleCount,valid_sampleCount_with_other_parameter_varies:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:sampleCount,various_sampleCount_with_all_formats:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:sample_count,1d_2d_array_3d:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,1d_texture:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,2d_texture,compressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,2d_texture,uncompressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,3d_texture,compressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,3d_texture,uncompressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,default_value_and_smallest_size,compressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_size,default_value_and_smallest_size,uncompressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:texture_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:viewFormats:*'>
+<meta name=variant content='?q=webgpu:api,validation,createTexture:zero_size_and_usage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createView/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createView/cts.https.html
new file mode 100644
index 0000000000..79e455e6ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/createView/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,createView:array_layers:*'>
+<meta name=variant content='?q=webgpu:api,validation,createView:aspect:*'>
+<meta name=variant content='?q=webgpu:api,validation,createView:cube_faces_square:*'>
+<meta name=variant content='?q=webgpu:api,validation,createView:dimension:*'>
+<meta name=variant content='?q=webgpu:api,validation,createView:format:*'>
+<meta name=variant content='?q=webgpu:api,validation,createView:mip_levels:*'>
+<meta name=variant content='?q=webgpu:api,validation,createView:texture_state:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/debugMarker/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/debugMarker/cts.https.html
new file mode 100644
index 0000000000..4903648a02
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/debugMarker/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,debugMarker:push_pop_call_count_unbalance,command_encoder:*'>
+<meta name=variant content='?q=webgpu:api,validation,debugMarker:push_pop_call_count_unbalance,render_compute_pass:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginComputePass/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginComputePass/cts.https.html
new file mode 100644
index 0000000000..cf10f04969
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginComputePass/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginComputePass:timestampWrites,invalid_query_set:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginComputePass:timestampWrites,query_index:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginComputePass:timestampWrites,query_set_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginComputePass:timestamp_query_set,device_mismatch:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginRenderPass/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginRenderPass/cts.https.html
new file mode 100644
index 0000000000..8a2b10dcfb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/beginRenderPass/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginRenderPass:color_attachments,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginRenderPass:depth_stencil_attachment,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginRenderPass:occlusion_query_set,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,beginRenderPass:timestamp_query_set,device_mismatch:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/clearBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/clearBuffer/cts.https.html
new file mode 100644
index 0000000000..87d5361637
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/clearBuffer/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:buffer_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:default_args:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:out_of_bounds:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:overflow:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,clearBuffer:size_alignment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/compute_pass/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/compute_pass/cts.https.html
new file mode 100644
index 0000000000..b869585171
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/compute_pass/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,compute_pass:dispatch_sizes:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer,usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,compute_pass:indirect_dispatch_buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,compute_pass:pipeline,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,compute_pass:set_pipeline:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyBufferToBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyBufferToBuffer/cts.https.html
new file mode 100644
index 0000000000..e7df6af4cf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyBufferToBuffer/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:buffer_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_out_of_bounds:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_overflow:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_size_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyBufferToBuffer:copy_within_same_buffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyTextureToTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyTextureToTexture/cts.https.html
new file mode 100644
index 0000000000..9e06834cb8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/copyTextureToTexture/cts.https.html
@@ -0,0 +1,47 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_aspects:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_ranges_with_compressed_texture_formats:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_with_invalid_or_destroyed_texture:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:copy_within_same_texture:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:depth_stencil_copy_restrictions:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:mipmap_level:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:multisampled_copy_restrictions:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_format_compatibility:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,copyTextureToTexture:texture_usage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/debug/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/debug/cts.https.html
new file mode 100644
index 0000000000..5eaa4060d5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/debug/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,debug:debug_group:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,debug:debug_group_balanced:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,debug:debug_marker:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/index_access/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/index_access/cts.https.html
new file mode 100644
index 0000000000..bfac5c6de3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/index_access/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,index_access:out_of_bounds:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,index_access:out_of_bounds_zero_sized_index_buffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/draw/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/draw/cts.https.html
new file mode 100644
index 0000000000..b6d0101d4f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/draw/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,draw:buffer_binding_overlap:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,draw:index_buffer_OOB:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,draw:last_buffer_setting_take_account:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,draw:max_draw_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,draw:unused_buffer_bound:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,draw:vertex_buffer_OOB:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/dynamic_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/dynamic_state/cts.https.html
new file mode 100644
index 0000000000..36b77040ca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/dynamic_state/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setBlendConstant:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setScissorRect,x_y_width_height_nonnegative:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setScissorRect,xy_rect_contained_in_attachment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setStencilReference:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setViewport,depth_rangeAndOrder:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setViewport,x_y_width_height_nonnegative:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,dynamic_state:setViewport,xy_rect_contained_in_attachment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/indirect_draw/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/indirect_draw/cts.https.html
new file mode 100644
index 0000000000..555667d353
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/indirect_draw/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_buffer_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,indirect_draw:indirect_offset_oob:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setIndexBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setIndexBuffer/cts.https.html
new file mode 100644
index 0000000000..cbdc370ae7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setIndexBuffer/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setIndexBuffer:index_buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setIndexBuffer:index_buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setIndexBuffer:index_buffer_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setIndexBuffer:offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setIndexBuffer:offset_and_size_oob:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setPipeline/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setPipeline/cts.https.html
new file mode 100644
index 0000000000..cbc3583603
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setPipeline/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setPipeline:invalid_pipeline:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setPipeline:pipeline,device_mismatch:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setVertexBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setVertexBuffer/cts.https.html
new file mode 100644
index 0000000000..09b63d67c7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/setVertexBuffer/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setVertexBuffer:offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setVertexBuffer:offset_and_size_oob:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setVertexBuffer:slot:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setVertexBuffer:vertex_buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setVertexBuffer:vertex_buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,setVertexBuffer:vertex_buffer_usage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/state_tracking/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/state_tracking/cts.https.html
new file mode 100644
index 0000000000..7587f424d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/render/state_tracking/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,state_tracking:all_needed_index_buffer_should_be_bound:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,state_tracking:all_needed_vertex_buffer_should_be_bound:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,state_tracking:vertex_buffers_do_not_inherit_between_render_passes:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,render,state_tracking:vertex_buffers_inherit_from_previous_pipeline:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/setBindGroup/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/setBindGroup/cts.https.html
new file mode 100644
index 0000000000..94ff6af04b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/cmds/setBindGroup/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,setBindGroup:bind_group,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,setBindGroup:buffer_dynamic_offsets:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_match_expectations_in_pass_encoder:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,setBindGroup:dynamic_offsets_passed_but_not_expected:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,setBindGroup:state_and_binding_index:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,cmds,setBindGroup:u32array_start_and_length:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/createRenderBundleEncoder/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/createRenderBundleEncoder/cts.https.html
new file mode 100644
index 0000000000..b92f6fc277
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/createRenderBundleEncoder/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,empty_color_formats:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachmentBytesPerSample,aligned:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachmentBytesPerSample,unaligned:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:attachment_state,limits,maxColorAttachments:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:depth_stencil_readonly:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:depth_stencil_readonly_with_undefined_depth:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,createRenderBundleEncoder:valid_texture_formats:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_open_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_open_state/cts.https.html
new file mode 100644
index 0000000000..4b5f7be9c3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_open_state/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_open_state:compute_pass_commands:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_open_state:non_pass_commands:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_open_state:render_bundle_commands:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_open_state:render_pass_commands:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_state/cts.https.html
new file mode 100644
index 0000000000..62e9b103ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/encoder_state/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_state:call_after_successful_finish:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_state:pass_end_invalid_order:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_state:pass_end_none:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_state:pass_end_twice,basic:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,encoder_state:pass_end_twice,render_pass_invalid:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html
new file mode 100644
index 0000000000..dd84adeb23
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_binding_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_resource_type_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bgl_visibility_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:bind_groups_and_pipeline_layout_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:buffer_binding,render_pipeline:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:empty_bind_group_layouts_requires_empty_bind_groups,compute_pass:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:empty_bind_group_layouts_requires_empty_bind_groups,render_pass:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:sampler_binding,render_pipeline:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/begin_end/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/begin_end/cts.https.html
new file mode 100644
index 0000000000..c9e4ac51a2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/begin_end/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,begin_end:nesting:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,begin_end:occlusion_query,begin_end_balance:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,begin_end:occlusion_query,begin_end_invalid_nesting:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,begin_end:occlusion_query,disjoint_queries_with_same_query_index:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/general/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/general/cts.https.html
new file mode 100644
index 0000000000..0c4e1a0ebf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/general/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,general:occlusion_query,invalid_query_set:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,general:occlusion_query,query_index:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,general:occlusion_query,query_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,general:timestamp_query,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,general:timestamp_query,invalid_query_set:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,general:timestamp_query,query_type_and_index:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/resolveQuerySet/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/resolveQuerySet/cts.https.html
new file mode 100644
index 0000000000..5d2e934c76
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/queries/resolveQuerySet/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,resolveQuerySet:destination_buffer_usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,resolveQuerySet:destination_offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,resolveQuerySet:first_query_and_query_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,resolveQuerySet:query_set_buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,resolveQuerySet:queryset_and_destination_buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,queries,resolveQuerySet:resolve_buffer_oob:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/render_bundle/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/render_bundle/cts.https.html
new file mode 100644
index 0000000000..781440e95b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/encoding/render_bundle/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,encoding,render_bundle:color_formats_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,render_bundle:depth_stencil_formats_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,render_bundle:depth_stencil_readonly_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,render_bundle:device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,render_bundle:empty_bundle_list:*'>
+<meta name=variant content='?q=webgpu:api,validation,encoding,render_bundle:sample_count_mismatch:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/error_scope/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/error_scope/cts.https.html
new file mode 100644
index 0000000000..f831dde609
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/error_scope/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,error_scope:balanced_nesting:*'>
+<meta name=variant content='?q=webgpu:api,validation,error_scope:balanced_siblings:*'>
+<meta name=variant content='?q=webgpu:api,validation,error_scope:current_scope:*'>
+<meta name=variant content='?q=webgpu:api,validation,error_scope:empty:*'>
+<meta name=variant content='?q=webgpu:api,validation,error_scope:parent_scope:*'>
+<meta name=variant content='?q=webgpu:api,validation,error_scope:simple:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/getBindGroupLayout/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/getBindGroupLayout/cts.https.html
new file mode 100644
index 0000000000..b6c6960cde
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/getBindGroupLayout/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,getBindGroupLayout:index_range,auto_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,getBindGroupLayout:index_range,explicit_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,getBindGroupLayout:unique_js_object,auto_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,getBindGroupLayout:unique_js_object,explicit_layout:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/gpu_external_texture_expiration/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/gpu_external_texture_expiration/cts.https.html
new file mode 100644
index 0000000000..585c4806ad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/gpu_external_texture_expiration/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,gpu_external_texture_expiration:import_and_use_in_different_microtask:*'>
+<meta name=variant content='?q=webgpu:api,validation,gpu_external_texture_expiration:import_and_use_in_different_task:*'>
+<meta name=variant content='?q=webgpu:api,validation,gpu_external_texture_expiration:import_from_different_video_frame:*'>
+<meta name=variant content='?q=webgpu:api,validation,gpu_external_texture_expiration:import_multiple_times_in_same_task_scope:*'>
+<meta name=variant content='?q=webgpu:api,validation,gpu_external_texture_expiration:use_import_to_refresh:*'>
+<meta name=variant content='?q=webgpu:api,validation,gpu_external_texture_expiration:webcodec_video_frame_close_expire_immediately:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_related/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_related/cts.https.html
new file mode 100644
index 0000000000..fb3eca44db
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_related/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_related:buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_related:buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_related:bytes_per_row_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_related:usage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_texture_copies/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_texture_copies/cts.https.html
new file mode 100644
index 0000000000..06655dcd49
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/buffer_texture_copies/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_offset:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_buffer_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_texture_copies:depth_stencil_format,copy_usage_and_aspect:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_texture_copies:device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_texture_copies:sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,buffer_texture_copies:texture_buffer_usages:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/layout_related/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/layout_related/cts.https.html
new file mode 100644
index 0000000000..33aaca2a49
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/layout_related/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:bound_on_bytes_per_row:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:bound_on_offset:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:bound_on_rows_per_image:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:copy_end_overflows_u64:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:required_bytes_in_copy:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,layout_related:rows_per_image_alignment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/texture_related/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/texture_related/cts.https.html
new file mode 100644
index 0000000000..63e7f5ebdf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/image_copy/texture_related/cts.https.html
@@ -0,0 +1,44 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:copy_rectangle:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:format:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:mip_level:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:origin_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:size_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:texture,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,image_copy,texture_related:valid:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/create/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/create/cts.https.html
new file mode 100644
index 0000000000..a2aebeed0b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/create/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,query_set,create:count:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/destroy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/destroy/cts.https.html
new file mode 100644
index 0000000000..e4b6083996
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/query_set/destroy/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,query_set,destroy:invalid_queryset:*'>
+<meta name=variant content='?q=webgpu:api,validation,query_set,destroy:twice:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/buffer_mapped/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/buffer_mapped/cts.https.html
new file mode 100644
index 0000000000..2037e3a72c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/buffer_mapped/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,buffer_mapped:copyBufferToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,buffer_mapped:copyBufferToTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,buffer_mapped:copyTextureToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,buffer_mapped:map_command_recording_order:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,buffer_mapped:writeBuffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture/cts.https.html
new file mode 100644
index 0000000000..fa933b4c8d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture/cts.https.html
@@ -0,0 +1,47 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:OOB,destination:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:OOB,source:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,format:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,mipLevel:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,state:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:destination_texture,usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_canvas,state:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_image,crossOrigin:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_imageBitmap,state:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:source_offscreenCanvas,state:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/buffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/buffer/cts.https.html
new file mode 100644
index 0000000000..445f2c1276
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/buffer/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:copyBufferToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:copyBufferToTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:copyTextureToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:resolveQuerySet:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:setBindGroup:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:setIndexBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:setVertexBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,buffer:writeBuffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/query_set/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/query_set/cts.https.html
new file mode 100644
index 0000000000..42db3853c2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/query_set/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,query_set:beginOcclusionQuery:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,query_set:resolveQuerySet:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,query_set:writeTimestamp:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/texture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/texture/cts.https.html
new file mode 100644
index 0000000000..8a5516d0ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/destroyed/texture/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,texture:beginRenderPass:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,texture:copyBufferToTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,texture:copyTextureToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,texture:copyTextureToTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,texture:setBindGroup:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,destroyed,texture:writeTexture:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/submit/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/submit/cts.https.html
new file mode 100644
index 0000000000..83c6eed982
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/submit/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,submit:command_buffer,device_mismatch:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeBuffer/cts.https.html
new file mode 100644
index 0000000000..7242c38153
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeBuffer/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,writeBuffer:buffer,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,writeBuffer:buffer_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,writeBuffer:ranges:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,writeBuffer:usages:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeTexture/cts.https.html
new file mode 100644
index 0000000000..dd411ba326
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/queue/writeTexture/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,queue,writeTexture:sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,writeTexture:texture,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,writeTexture:texture_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,queue,writeTexture:usages:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/attachment_compatibility/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/attachment_compatibility/cts.https.html
new file mode 100644
index 0000000000..b079e7178d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/attachment_compatibility/cts.https.html
@@ -0,0 +1,47 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,color_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,color_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,color_sparse:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,depth_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_and_bundle,sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,color_sparse:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,depth_stencil_read_only_write_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,attachment_compatibility:render_pass_or_bundle_and_pipeline,sample_count:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/render_pass_descriptor/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/render_pass_descriptor/cts.https.html
new file mode 100644
index 0000000000..7831601209
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/render_pass_descriptor/cts.https.html
@@ -0,0 +1,62 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:attachments,color_depth_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:attachments,layer_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:attachments,mip_level_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:attachments,one_color_attachment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:attachments,one_depth_stencil_attachment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:attachments,same_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,empty:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,aligned:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,limits,maxColorAttachments:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,non_multisampled:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,depth_clear_value:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadOnly:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:depth_stencil_attachment,sample_counts_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:occlusionQuerySet,query_set_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,array_layer_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,different_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,different_size:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,error_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,format_supports_resolve:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,mipmap_level_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,single_sample_count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,usage:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:timestampWrite,query_index:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pass,render_pass_descriptor:timestampWrites,query_set_type:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/resolve/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/resolve/cts.https.html
new file mode 100644
index 0000000000..feb783e5b5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pass/resolve/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pass,resolve:resolve_attachment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/depth_stencil_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/depth_stencil_state/cts.https.html
new file mode 100644
index 0000000000..c8ec67c443
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/depth_stencil_state/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:depthCompare_optional:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:depthWriteEnabled_optional:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:depth_test:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:depth_write,frag_depth:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:depth_write:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:stencil_test:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,depth_stencil_state:stencil_write:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/fragment_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/fragment_state/cts.https.html
new file mode 100644
index 0000000000..a02e126c27
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/fragment_state/cts.https.html
@@ -0,0 +1,45 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:color_target_exists:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:limits,maxColorAttachmentBytesPerSample,aligned:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:limits,maxColorAttachmentBytesPerSample,unaligned:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:limits,maxColorAttachments:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets,blend:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:pipeline_output_targets:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:targets_blend:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:targets_format_filterable:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:targets_format_renderable:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,fragment_state:targets_write_mask:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/inter_stage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/inter_stage/cts.https.html
new file mode 100644
index 0000000000..2ffaca83bd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/inter_stage/cts.https.html
@@ -0,0 +1,44 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:interpolation_sampling:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:interpolation_type:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:location,mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:location,subset:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:location,superset:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:max_components_count,input:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:max_components_count,output:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,inter_stage:type:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/misc/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/misc/cts.https.html
new file mode 100644
index 0000000000..df06d2c151
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/misc/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,misc:basic:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,misc:pipeline_layout,device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,misc:vertex_state_only:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/multisample_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/multisample_state/cts.https.html
new file mode 100644
index 0000000000..188607b1f1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/multisample_state/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,multisample_state:alpha_to_coverage,count:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,multisample_state:alpha_to_coverage,sample_mask:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,multisample_state:count:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/overrides/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/overrides/cts.https.html
new file mode 100644
index 0000000000..f16e6a1008
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/overrides/cts.https.html
@@ -0,0 +1,45 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:identifier,fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:identifier,vertex:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:uninitialized,fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:uninitialized,vertex:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:value,type_error,fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:value,type_error,vertex:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:value,validation_error,f16,fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:value,validation_error,f16,vertex:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:value,validation_error,fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,overrides:value,validation_error,vertex:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/primitive_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/primitive_state/cts.https.html
new file mode 100644
index 0000000000..b9783003ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/primitive_state/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,primitive_state:strip_index_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,primitive_state:unclipped_depth:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/shader_module/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/shader_module/cts.https.html
new file mode 100644
index 0000000000..49286761e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/shader_module/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,shader_module:device_mismatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,shader_module:invalid,fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,shader_module:invalid,vertex:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/vertex_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/vertex_state/cts.https.html
new file mode 100644
index 0000000000..a32b8c4881
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/render_pipeline/vertex_state/cts.https.html
@@ -0,0 +1,47 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:many_attributes_overlapping:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:max_vertex_attribute_limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:max_vertex_buffer_array_stride_limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:max_vertex_buffer_limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_contained_in_stride:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_offset_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_shaderLocation_limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_attribute_shaderLocation_unique:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_buffer_array_stride_limit_alignment:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_input_location_in_vertex_state:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_input_location_limit:*'>
+<meta name=variant content='?q=webgpu:api,validation,render_pipeline,vertex_state:vertex_shader_type_matches_attribute_format:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_encoder/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_encoder/cts.https.html
new file mode 100644
index 0000000000..22a5a8462f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_encoder/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_compute_pass_with_two_dispatches:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_compute_pass_with_no_dispatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_compute_pass_with_one_dispatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_render_pass_with_no_draw:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_render_pass_with_one_draw:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_encoder:subresources,buffer_usage_in_one_render_pass_with_two_draws:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_misc/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_misc/cts.https.html
new file mode 100644
index 0000000000..f2d0204a6b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/buffer/in_pass_misc/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_misc:subresources,buffer_usages_in_copy_and_pass:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_misc:subresources,reset_buffer_usage_before_dispatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,buffer,in_pass_misc:subresources,reset_buffer_usage_before_draw:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_pass_encoder/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_pass_encoder/cts.https.html
new file mode 100644
index 0000000000..8db14cd48b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_pass_encoder/cts.https.html
@@ -0,0 +1,46 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:bindings_in_bundle:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:replaced_binding:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,basic,render:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,dispatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,pass_boundary,compute:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,pass_boundary,render:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:shader_stages_and_visibility,attachment_write:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:shader_stages_and_visibility,storage_write:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_aspect:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:subresources_and_binding_types_combination_for_color:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:unused_bindings_in_pipeline:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_common/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_common/cts.https.html
new file mode 100644
index 0000000000..11f1657b16
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_common/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_common:subresources,color_attachment_and_bind_group:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_common:subresources,color_attachments:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_attachment_and_bind_group:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_common:subresources,depth_stencil_texture_in_bind_groups:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_common:subresources,multiple_bind_groups:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_misc/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_misc/cts.https.html
new file mode 100644
index 0000000000..63122c1bd2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/resource_usages/texture/in_render_misc/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_bind_group_on_same_index_color_texture:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_bind_group_on_same_index_depth_stencil_texture:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,set_unused_bind_group:*'>
+<meta name=variant content='?q=webgpu:api,validation,resource_usages,texture,in_render_misc:subresources,texture_usages_in_copy_and_render_pass:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/entry_point/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/entry_point/cts.https.html
new file mode 100644
index 0000000000..b929adfd6e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/entry_point/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,shader_module,entry_point:compute:*'>
+<meta name=variant content='?q=webgpu:api,validation,shader_module,entry_point:fragment:*'>
+<meta name=variant content='?q=webgpu:api,validation,shader_module,entry_point:vertex:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/overrides/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/overrides/cts.https.html
new file mode 100644
index 0000000000..10e50dbff9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/shader_module/overrides/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,shader_module,overrides:id_conflict:*'>
+<meta name=variant content='?q=webgpu:api,validation,shader_module,overrides:name_conflict:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/state/device_lost/destroy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/state/device_lost/destroy/cts.https.html
new file mode 100644
index 0000000000..52f3ab7ca0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/state/device_lost/destroy/cts.https.html
@@ -0,0 +1,67 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,clearBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,computePass,dispatch:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,copyBufferToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,copyBufferToTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,copyTextureToBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,copyTextureToTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,renderPass,draw:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,renderPass,renderBundle:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,resolveQuerySet:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:command,writeTimestamp:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createBindGroup:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createBindGroupLayout:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createCommandEncoder:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createComputePipeline:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createComputePipelineAsync:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createPipelineLayout:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createQuerySet:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createRenderBundleEncoder:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createRenderPipeline:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createRenderPipelineAsync:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createSampler:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createShaderModule:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createTexture,2d,compressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createTexture,2d,uncompressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createView,2d,compressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:createView,2d,uncompressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:importExternalTexture:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture,canvas:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,copyExternalImageToTexture,imageBitmap:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeBuffer:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeTexture,2d,compressed_format:*'>
+<meta name=variant content='?q=webgpu:api,validation,state,device_lost,destroy:queue,writeTexture,2d,uncompressed_format:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/bgra8unorm_storage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/bgra8unorm_storage/cts.https.html
new file mode 100644
index 0000000000..a2c94e7147
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/bgra8unorm_storage/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,texture,bgra8unorm_storage:configure_storage_usage_on_canvas_context_with_bgra8unorm_storage:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,bgra8unorm_storage:configure_storage_usage_on_canvas_context_without_bgra8unorm_storage:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,bgra8unorm_storage:create_bind_group_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,bgra8unorm_storage:create_shader_module_with_bgra8unorm_storage:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,bgra8unorm_storage:create_shader_module_without_bgra8unorm_storage:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,bgra8unorm_storage:create_texture:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/destroy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/destroy/cts.https.html
new file mode 100644
index 0000000000..42e241b891
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/destroy/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,texture,destroy:base:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,destroy:invalid_texture:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,destroy:submit_a_destroyed_texture_as_attachment:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,destroy:twice:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/float32_filterable/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/float32_filterable/cts.https.html
new file mode 100644
index 0000000000..2981e6e74f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/float32_filterable/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,texture,float32_filterable:create_bind_group:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/rg11b10ufloat_renderable/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/rg11b10ufloat_renderable/cts.https.html
new file mode 100644
index 0000000000..8815ad5b87
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/api/validation/texture/rg11b10ufloat_renderable/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_bundle_encoder:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_pass_msaa_and_resolve:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_pass_single_sampled:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,rg11b10ufloat_renderable:create_render_pipeline:*'>
+<meta name=variant content='?q=webgpu:api,validation,texture,rg11b10ufloat_renderable:create_texture:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer/cts.https.html
new file mode 100644
index 0000000000..e1f7b26458
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,encoding,cmds,copyTextureToBuffer:compressed:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html
new file mode 100644
index 0000000000..a6f775b89f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,unused:*'>
+<meta name=variant content='?q=webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,used:*'>
+<meta name=variant content='?q=webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,unused:*'>
+<meta name=variant content='?q=webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,render_pass,used:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/fragment_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/fragment_state/cts.https.html
new file mode 100644
index 0000000000..c7ae7c8c3f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/fragment_state/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/shader_module/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/shader_module/cts.https.html
new file mode 100644
index 0000000000..9349c3b0de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/shader_module/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/vertex_state/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/vertex_state/cts.https.html
new file mode 100644
index 0000000000..4475081763
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/render_pipeline/vertex_state/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/createTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/createTexture/cts.https.html
new file mode 100644
index 0000000000..9151e96f32
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/createTexture/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,texture,createTexture:unsupportedTextureFormats:*'>
+<meta name=variant content='?q=webgpu:compat,api,validation,texture,createTexture:unsupportedTextureViewFormats:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/cubeArray/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/cubeArray/cts.https.html
new file mode 100644
index 0000000000..814ed23821
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/compat/api/validation/texture/cubeArray/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:compat,api,validation,texture,cubeArray:cube_array:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/examples/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/examples/cts.https.html
new file mode 100644
index 0000000000..f28a97f5c4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/examples/cts.https.html
@@ -0,0 +1,50 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:examples:basic,async:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_cases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_cases_subcases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_subcases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,builder_subcases_short:*'>
+<meta name=variant content='?q=webgpu:examples:basic,plain_cases:*'>
+<meta name=variant content='?q=webgpu:examples:basic,plain_cases_private:*'>
+<meta name=variant content='?q=webgpu:examples:basic:*'>
+<meta name=variant content='?q=webgpu:examples:gpu,async:*'>
+<meta name=variant content='?q=webgpu:examples:gpu,buffers:*'>
+<meta name=variant content='?q=webgpu:examples:gpu,with_texture_compression,bc:*'>
+<meta name=variant content='?q=webgpu:examples:gpu,with_texture_compression,etc2:*'>
+<meta name=variant content='?q=webgpu:examples:not_implemented_yet,with_plan:*'>
+<meta name=variant content='?q=webgpu:examples:not_implemented_yet,without_plan:*'>
+<meta name=variant content='?q=webgpu:examples:test_name:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/idl/constants/flags/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/idl/constants/flags/cts.https.html
new file mode 100644
index 0000000000..184a2e3e7a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/idl/constants/flags/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:idl,constants,flags:BufferUsage,count:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:BufferUsage,values:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:ColorWrite,count:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:ColorWrite,values:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:ShaderStage,count:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:ShaderStage,values:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:TextureUsage,count:*'>
+<meta name=variant content='?q=webgpu:idl,constants,flags:TextureUsage,values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_addition/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_addition/cts.https.html
new file mode 100644
index 0000000000..4c722ce5c7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_addition/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_addition:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_addition:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_addition:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_addition:vector_scalar:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_comparison/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_comparison/cts.https.html
new file mode 100644
index 0000000000..0714a91a01
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_comparison/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_comparison:equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_comparison:greater_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_comparison:greater_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_comparison:less_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_comparison:less_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_comparison:not_equals:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_division/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_division/cts.https.html
new file mode 100644
index 0000000000..2b5b3fb890
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_division/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_division:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_division:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_division:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_division:vector_scalar:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_addition/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_addition/cts.https.html
new file mode 100644
index 0000000000..081d5b1706
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_addition/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_subtraction/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_subtraction/cts.https.html
new file mode 100644
index 0000000000..df02c28d16
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_matrix_subtraction/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_matrix_subtraction:matrix:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_multiplication/cts.https.html
new file mode 100644
index 0000000000..627677e388
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_multiplication/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_multiplication:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_multiplication:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_multiplication:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_multiplication:vector_scalar:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_remainder/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_remainder/cts.https.html
new file mode 100644
index 0000000000..fd65a1672e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_remainder/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_remainder:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_remainder:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_remainder:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_remainder:vector_scalar:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_subtraction/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_subtraction/cts.https.html
new file mode 100644
index 0000000000..9ffff3d938
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/af_subtraction/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_subtraction:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_subtraction:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_subtraction:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,af_subtraction:vector_scalar:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise/cts.https.html
new file mode 100644
index 0000000000..9c545d87f9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise:bitwise_and:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise:bitwise_and_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise:bitwise_exclusive_or_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise:bitwise_or:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise:bitwise_or_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise_shift/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise_shift/cts.https.html
new file mode 100644
index 0000000000..800dd394c8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bitwise_shift/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise_shift:shift_left_concrete:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise_shift:shift_left_concrete_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise_shift:shift_right_concrete:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bitwise_shift:shift_right_concrete_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bool_logical/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bool_logical/cts.https.html
new file mode 100644
index 0000000000..7519dd7268
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/bool_logical/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:and:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:and_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:and_short_circuit:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:not_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:or:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:or_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,bool_logical:or_short_circuit:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_addition/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_addition/cts.https.html
new file mode 100644
index 0000000000..7d01d9922c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_addition/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_addition:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_addition:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_addition:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_addition:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_addition:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_addition:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_comparison/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_comparison/cts.https.html
new file mode 100644
index 0000000000..f17f0137b2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_comparison/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_comparison:equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_comparison:greater_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_comparison:greater_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_comparison:less_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_comparison:less_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_comparison:not_equals:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_division/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_division/cts.https.html
new file mode 100644
index 0000000000..e7452fbdfa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_division/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_division:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_division:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_division:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_division:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_division:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_division:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_addition/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_addition/cts.https.html
new file mode 100644
index 0000000000..81c1950e75
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_addition/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_addition:matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_addition:matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication/cts.https.html
new file mode 100644
index 0000000000..44b0ecc06b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_matrix_multiplication:matrix_matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_matrix_multiplication:matrix_matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication/cts.https.html
new file mode 100644
index 0000000000..0aca44f2e6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_scalar_multiplication:matrix_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_scalar_multiplication:matrix_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_scalar_multiplication:scalar_matrix:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_subtraction/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_subtraction/cts.https.html
new file mode 100644
index 0000000000..3e105b2f9e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_subtraction/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_subtraction:matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_subtraction:matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication/cts.https.html
new file mode 100644
index 0000000000..13971b60bd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_vector_multiplication:matrix_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_vector_multiplication:vector_matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_matrix_vector_multiplication:vector_matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_multiplication/cts.https.html
new file mode 100644
index 0000000000..2691af1e54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_multiplication/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_multiplication:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_multiplication:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_multiplication:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_multiplication:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_multiplication:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_remainder/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_remainder/cts.https.html
new file mode 100644
index 0000000000..7ad29ee0a8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_remainder/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_remainder:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_remainder:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_remainder:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_remainder:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_remainder:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_remainder:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_subtraction/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_subtraction/cts.https.html
new file mode 100644
index 0000000000..cf4065131e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f16_subtraction/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_subtraction:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_subtraction:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_subtraction:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_subtraction:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f16_subtraction:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_addition/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_addition/cts.https.html
new file mode 100644
index 0000000000..2b73075221
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_addition/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_addition:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_addition:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_addition:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_addition:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_addition:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_addition:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_comparison/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_comparison/cts.https.html
new file mode 100644
index 0000000000..ff3b39f276
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_comparison/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_comparison:equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_comparison:greater_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_comparison:greater_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_comparison:less_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_comparison:less_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_comparison:not_equals:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_division/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_division/cts.https.html
new file mode 100644
index 0000000000..5ce261f13d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_division/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_division:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_division:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_division:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_division:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_division:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_division:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_addition/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_addition/cts.https.html
new file mode 100644
index 0000000000..1e9ebee999
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_addition/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_addition:matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_addition:matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication/cts.https.html
new file mode 100644
index 0000000000..a90b06e6a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_matrix_multiplication:matrix_matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_matrix_multiplication:matrix_matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication/cts.https.html
new file mode 100644
index 0000000000..84239b2805
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_scalar_multiplication:matrix_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_scalar_multiplication:matrix_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_scalar_multiplication:scalar_matrix:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_subtraction/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_subtraction/cts.https.html
new file mode 100644
index 0000000000..7a659b21c8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_subtraction/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_subtraction:matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_subtraction:matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication/cts.https.html
new file mode 100644
index 0000000000..ab9765b662
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:matrix_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:vector_matrix:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_matrix_vector_multiplication:vector_matrix_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_multiplication/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_multiplication/cts.https.html
new file mode 100644
index 0000000000..1f8859285f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_multiplication/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_multiplication:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_multiplication:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_multiplication:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_multiplication:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_multiplication:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_remainder/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_remainder/cts.https.html
new file mode 100644
index 0000000000..3a22ef3e35
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_remainder/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_remainder:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_remainder:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_remainder:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_remainder:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_remainder:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_subtraction/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_subtraction/cts.https.html
new file mode 100644
index 0000000000..d987a81953
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/f32_subtraction/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_subtraction:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_subtraction:scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_subtraction:scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_subtraction:vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,f32_subtraction:vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_arithmetic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_arithmetic/cts.https.html
new file mode 100644
index 0000000000..d6c31c74f4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_arithmetic/cts.https.html
@@ -0,0 +1,60 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:addition:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:addition_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:addition_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:addition_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:addition_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:division:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:division_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:division_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:division_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:division_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:multiplication_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:remainder:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:remainder_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_arithmetic:subtraction_vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_comparison/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_comparison/cts.https.html
new file mode 100644
index 0000000000..48b7383c4b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/i32_comparison/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_comparison:equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_comparison:greater_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_comparison:greater_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_comparison:less_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_comparison:less_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,i32_comparison:not_equals:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_arithmetic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_arithmetic/cts.https.html
new file mode 100644
index 0000000000..e61e01f67d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_arithmetic/cts.https.html
@@ -0,0 +1,60 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:addition:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:addition_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:addition_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:addition_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:addition_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:division:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:division_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:division_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:division_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:division_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:multiplication_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:remainder:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:remainder_vector_scalar_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_compound:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_scalar_vector:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_vector_scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_arithmetic:subtraction_vector_scalar_compound:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_comparison/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_comparison/cts.https.html
new file mode 100644
index 0000000000..5313f7fb2f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/binary/u32_comparison/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_comparison:equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_comparison:greater_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_comparison:greater_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_comparison:less_equals:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_comparison:less_than:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,binary,u32_comparison:not_equals:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/abs/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/abs/cts.https.html
new file mode 100644
index 0000000000..79fc577984
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/abs/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,abs:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,abs:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,abs:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,abs:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,abs:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acos/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acos/cts.https.html
new file mode 100644
index 0000000000..227e01e414
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acos/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,acos:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,acos:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,acos:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acosh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acosh/cts.https.html
new file mode 100644
index 0000000000..4ab5bf4007
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/acosh/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,acosh:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,acosh:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,acosh:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/all/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/all/cts.https.html
new file mode 100644
index 0000000000..5124a65016
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/all/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,all:bool:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/any/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/any/cts.https.html
new file mode 100644
index 0000000000..9a52284a45
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/any/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,any:bool:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/arrayLength/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/arrayLength/cts.https.html
new file mode 100644
index 0000000000..a22166b3fe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/arrayLength/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,arrayLength:binding_subregion:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,arrayLength:multiple_elements:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,arrayLength:read_only:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,arrayLength:single_element:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,arrayLength:struct_member:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asin/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asin/cts.https.html
new file mode 100644
index 0000000000..79f85480bd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asin/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,asin:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,asin:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,asin:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asinh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asinh/cts.https.html
new file mode 100644
index 0000000000..6f42312c79
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/asinh/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,asinh:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,asinh:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,asinh:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan/cts.https.html
new file mode 100644
index 0000000000..6be2923d11
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atan:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atan:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atan:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan2/cts.https.html
new file mode 100644
index 0000000000..e812f0cf81
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atan2/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atan2:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atan2:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atan2:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atanh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atanh/cts.https.html
new file mode 100644
index 0000000000..93e98b5fbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atanh/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atanh:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atanh:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atanh:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd/cts.https.html
new file mode 100644
index 0000000000..b3f33950df
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicAdd:add_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicAdd:add_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd/cts.https.html
new file mode 100644
index 0000000000..f7cfad1430
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicAnd:and_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicAnd:and_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak/cts.https.html
new file mode 100644
index 0000000000..9b91f11667
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_storage_advanced:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_storage_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_workgroup_advanced:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicCompareExchangeWeak:compare_exchange_weak_workgroup_basic:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange/cts.https.html
new file mode 100644
index 0000000000..166c59f0d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_storage_advanced:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_storage_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_workgroup_advanced:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicExchange:exchange_workgroup_basic:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad/cts.https.html
new file mode 100644
index 0000000000..dc772afaa7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicLoad:load_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicLoad:load_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax/cts.https.html
new file mode 100644
index 0000000000..5af2c8b011
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicMax:max_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicMax:max_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin/cts.https.html
new file mode 100644
index 0000000000..545d5bca3e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicMin:min_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicMin:min_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr/cts.https.html
new file mode 100644
index 0000000000..68fb0114a0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicOr:or_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicOr:or_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore/cts.https.html
new file mode 100644
index 0000000000..03e6e3b17c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_storage_advanced:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_storage_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_workgroup_advanced:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicStore:store_workgroup_basic:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub/cts.https.html
new file mode 100644
index 0000000000..67119e43aa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicSub:sub_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicSub:sub_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor/cts.https.html
new file mode 100644
index 0000000000..0a5b4393d7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicXor:xor_storage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,atomics,atomicXor:xor_workgroup:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/bitcast/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/bitcast/cts.https.html
new file mode 100644
index 0000000000..d61fbabd81
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/bitcast/cts.https.html
@@ -0,0 +1,57 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:f16_to_f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_u32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:f32_to_vec2h:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_u32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:i32_to_vec2h:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_u32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:u32_to_vec2h:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec2f_to_vec4h:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec2h_to_u32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec2i_to_vec4h:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec2u_to_vec4h:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2f:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2i:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,bitcast:vec4h_to_vec2u:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ceil/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ceil/cts.https.html
new file mode 100644
index 0000000000..2554977abc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ceil/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,ceil:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,ceil:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,ceil:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/clamp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/clamp/cts.https.html
new file mode 100644
index 0000000000..9b9d7504b3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/clamp/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,clamp:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,clamp:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,clamp:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,clamp:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,clamp:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,clamp:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cos/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cos/cts.https.html
new file mode 100644
index 0000000000..c1a7b467e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cos/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cos:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cos:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cos:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cosh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cosh/cts.https.html
new file mode 100644
index 0000000000..46040a4cc9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cosh/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cosh:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cosh:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cosh:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countLeadingZeros/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countLeadingZeros/cts.https.html
new file mode 100644
index 0000000000..df49f293ec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countLeadingZeros/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,countLeadingZeros:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,countLeadingZeros:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countOneBits/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countOneBits/cts.https.html
new file mode 100644
index 0000000000..40ffed8c59
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countOneBits/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,countOneBits:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,countOneBits:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countTrailingZeros/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countTrailingZeros/cts.https.html
new file mode 100644
index 0000000000..bb3b0baf39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/countTrailingZeros/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,countTrailingZeros:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,countTrailingZeros:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cross/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cross/cts.https.html
new file mode 100644
index 0000000000..3ac9484801
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/cross/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cross:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cross:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,cross:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/degrees/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/degrees/cts.https.html
new file mode 100644
index 0000000000..05b977b919
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/degrees/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,degrees:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,degrees:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,degrees:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/determinant/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/determinant/cts.https.html
new file mode 100644
index 0000000000..52cb54b9ea
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/determinant/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,determinant:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,determinant:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,determinant:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/distance/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/distance/cts.https.html
new file mode 100644
index 0000000000..3cbbe0182d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/distance/cts.https.html
@@ -0,0 +1,44 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,distance:f32_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dot/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dot/cts.https.html
new file mode 100644
index 0000000000..251705280d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dot/cts.https.html
@@ -0,0 +1,45 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:f32_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dot:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdx/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdx/cts.https.html
new file mode 100644
index 0000000000..a105d5e625
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdx/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dpdx:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxCoarse/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxCoarse/cts.https.html
new file mode 100644
index 0000000000..4e2df9c62d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxCoarse/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dpdxCoarse:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxFine/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxFine/cts.https.html
new file mode 100644
index 0000000000..1efe250d4a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdxFine/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dpdxFine:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdy/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdy/cts.https.html
new file mode 100644
index 0000000000..1fc2a0cfec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdy/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dpdy:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyCoarse/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyCoarse/cts.https.html
new file mode 100644
index 0000000000..f88a5333d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyCoarse/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dpdyCoarse:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyFine/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyFine/cts.https.html
new file mode 100644
index 0000000000..137cb1734c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/dpdyFine/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,dpdyFine:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp/cts.https.html
new file mode 100644
index 0000000000..b36da53209
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,exp:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,exp:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,exp:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp2/cts.https.html
new file mode 100644
index 0000000000..7e889f9268
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/exp2/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,exp2:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,exp2:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,exp2:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/extractBits/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/extractBits/cts.https.html
new file mode 100644
index 0000000000..47a328b129
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/extractBits/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,extractBits:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,extractBits:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/faceForward/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/faceForward/cts.https.html
new file mode 100644
index 0000000000..27f58bd167
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/faceForward/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,faceForward:f32_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstLeadingBit/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstLeadingBit/cts.https.html
new file mode 100644
index 0000000000..e0b98e877f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstLeadingBit/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,firstLeadingBit:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,firstLeadingBit:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstTrailingBit/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstTrailingBit/cts.https.html
new file mode 100644
index 0000000000..539d705bfb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/firstTrailingBit/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,firstTrailingBit:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,firstTrailingBit:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/floor/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/floor/cts.https.html
new file mode 100644
index 0000000000..415785f70d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/floor/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,floor:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,floor:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,floor:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fma/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fma/cts.https.html
new file mode 100644
index 0000000000..ddf014c449
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fma/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fma:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fma:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fma:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fract/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fract/cts.https.html
new file mode 100644
index 0000000000..85413ffa83
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fract/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fract:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fract:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fract:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/frexp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/frexp/cts.https.html
new file mode 100644
index 0000000000..5c94ddf50e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/frexp/cts.https.html
@@ -0,0 +1,51 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_vec2_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_vec2_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_vec3_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_vec3_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_vec4_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f16_vec4_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_vec2_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_vec2_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_vec3_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_vec3_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_vec4_exp:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,frexp:f32_vec4_fract:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidth/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidth/cts.https.html
new file mode 100644
index 0000000000..8e0edbdb16
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidth/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fwidth:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthCoarse/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthCoarse/cts.https.html
new file mode 100644
index 0000000000..60229b7e2a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthCoarse/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fwidthCoarse:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthFine/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthFine/cts.https.html
new file mode 100644
index 0000000000..2291ab8fca
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/fwidthFine/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,fwidthFine:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/insertBits/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/insertBits/cts.https.html
new file mode 100644
index 0000000000..caa3647c58
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/insertBits/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,insertBits:integer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/inversesqrt/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/inversesqrt/cts.https.html
new file mode 100644
index 0000000000..85d00c0f21
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/inversesqrt/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,inversesqrt:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,inversesqrt:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,inversesqrt:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ldexp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ldexp/cts.https.html
new file mode 100644
index 0000000000..feb2b61fc1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/ldexp/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,ldexp:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,ldexp:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,ldexp:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/length/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/length/cts.https.html
new file mode 100644
index 0000000000..7ae43aaac2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/length/cts.https.html
@@ -0,0 +1,44 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,length:f32_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log/cts.https.html
new file mode 100644
index 0000000000..8a1083f80e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,log:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,log:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,log:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log2/cts.https.html
new file mode 100644
index 0000000000..7a6eb24ce8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/log2/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,log2:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,log2:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,log2:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/max/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/max/cts.https.html
new file mode 100644
index 0000000000..e521101d43
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/max/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,max:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,max:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,max:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,max:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,max:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,max:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/min/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/min/cts.https.html
new file mode 100644
index 0000000000..95a2994b2b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/min/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/mix/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/mix/cts.https.html
new file mode 100644
index 0000000000..7b2cc5d90b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/mix/cts.https.html
@@ -0,0 +1,47 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:abstract_float_matching:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f16_matching:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f32_matching:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f32_nonmatching_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f32_nonmatching_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:f32_nonmatching_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/modf/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/modf/cts.https.html
new file mode 100644
index 0000000000..eac7d162cc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/modf/cts.https.html
@@ -0,0 +1,59 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_vec2_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_vec2_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_vec3_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_vec3_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_vec4_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_vec4_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:abstract_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_vec2_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_vec2_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_vec3_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_vec3_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_vec4_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_vec4_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f16_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_vec2_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_vec2_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_vec3_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_vec3_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_fract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_whole:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:f32_whole:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/normalize/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/normalize/cts.https.html
new file mode 100644
index 0000000000..bf162274e1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/normalize/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,normalize:f32_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16float/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16float/cts.https.html
new file mode 100644
index 0000000000..8bc2700cd7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16float/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pack2x16float:pack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16snorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16snorm/cts.https.html
new file mode 100644
index 0000000000..46f649d40b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16snorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pack2x16snorm:pack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16unorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16unorm/cts.https.html
new file mode 100644
index 0000000000..533bba82bb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack2x16unorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pack2x16unorm:pack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8snorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8snorm/cts.https.html
new file mode 100644
index 0000000000..142944c203
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8snorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pack4x8snorm:pack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8unorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8unorm/cts.https.html
new file mode 100644
index 0000000000..ca567dfd0d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pack4x8unorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pack4x8unorm:pack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pow/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pow/cts.https.html
new file mode 100644
index 0000000000..14d6c52f4a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/pow/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pow:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pow:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,pow:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/quantizeToF16/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/quantizeToF16/cts.https.html
new file mode 100644
index 0000000000..93f2074cf4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/quantizeToF16/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,quantizeToF16:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/radians/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/radians/cts.https.html
new file mode 100644
index 0000000000..87356cddd5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/radians/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,radians:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,radians:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,radians:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reflect/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reflect/cts.https.html
new file mode 100644
index 0000000000..b35b5c4fff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reflect/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reflect:f32_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/refract/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/refract/cts.https.html
new file mode 100644
index 0000000000..f4ac297425
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/refract/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:f16_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:f16_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:f16_vec4:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:f32_vec2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:f32_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,refract:f32_vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reverseBits/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reverseBits/cts.https.html
new file mode 100644
index 0000000000..f1ba1d9428
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/reverseBits/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reverseBits:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,reverseBits:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/round/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/round/cts.https.html
new file mode 100644
index 0000000000..9a0adaf43c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/round/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,round:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,round:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,round:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/saturate/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/saturate/cts.https.html
new file mode 100644
index 0000000000..773710f1d7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/saturate/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,saturate:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,saturate:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,saturate:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/select/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/select/cts.https.html
new file mode 100644
index 0000000000..7c72423a13
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/select/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,select:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,select:vector:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sign/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sign/cts.https.html
new file mode 100644
index 0000000000..a50dbbbccf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sign/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sign:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sign:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sign:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sign:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sign:i32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sin/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sin/cts.https.html
new file mode 100644
index 0000000000..fc87e545ba
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sin/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sin:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sin:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sin:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sinh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sinh/cts.https.html
new file mode 100644
index 0000000000..71f2190159
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sinh/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sinh:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sinh:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sinh:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/smoothstep/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/smoothstep/cts.https.html
new file mode 100644
index 0000000000..4631e443e8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/smoothstep/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,smoothstep:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,smoothstep:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,smoothstep:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sqrt/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sqrt/cts.https.html
new file mode 100644
index 0000000000..4fbb318173
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/sqrt/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sqrt:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sqrt:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,sqrt:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/step/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/step/cts.https.html
new file mode 100644
index 0000000000..6ab0f248da
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/step/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,step:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,step:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,step:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/storageBarrier/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/storageBarrier/cts.https.html
new file mode 100644
index 0000000000..a0cbfd9346
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/storageBarrier/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,storageBarrier:barrier:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,storageBarrier:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tan/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tan/cts.https.html
new file mode 100644
index 0000000000..99989980c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tan/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,tan:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,tan:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,tan:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tanh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tanh/cts.https.html
new file mode 100644
index 0000000000..150fd5bced
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/tanh/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,tanh:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,tanh:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,tanh:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureDimension/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureDimension/cts.https.html
new file mode 100644
index 0000000000..d71796e773
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureDimension/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureDimension:depth:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureDimension:external:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureDimension:sampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureDimension:storage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGather/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGather/cts.https.html
new file mode 100644
index 0000000000..7ff35e1ab8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGather/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:depth_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:depth_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:depth_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:depth_array_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:sampled_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:sampled_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:sampled_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:sampled_array_3d_coords:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGatherCompare/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGatherCompare/cts.https.html
new file mode 100644
index 0000000000..dfa2b3baa6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureGatherCompare/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:array_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:sampled_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:sampled_array_3d_coords:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureLoad/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureLoad/cts.https.html
new file mode 100644
index 0000000000..2b31e64a8d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureLoad/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:arrayed:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:depth:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:external:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:multisampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_1d:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_2d:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureLoad:sampled_3d:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLayers/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLayers/cts.https.html
new file mode 100644
index 0000000000..201e132eb1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLayers/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLayers:arrayed:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLayers:sampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLayers:storage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLevels/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLevels/cts.https.html
new file mode 100644
index 0000000000..9adeb8bf93
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumLevels/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLevels:depth:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumLevels:sampled:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumSamples/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumSamples/cts.https.html
new file mode 100644
index 0000000000..8f8d6d0169
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureNumSamples/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumSamples:depth:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureNumSamples:sampled:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSample/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSample/cts.https.html
new file mode 100644
index 0000000000..aa2a5211b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSample/cts.https.html
@@ -0,0 +1,46 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:control_flow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:depth_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:depth_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:depth_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:depth_array_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:sampled_1d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:sampled_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:sampled_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:sampled_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:sampled_array_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSample:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleBias/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleBias/cts.https.html
new file mode 100644
index 0000000000..4c044b6bbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleBias/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:control_flow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompare/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompare/cts.https.html
new file mode 100644
index 0000000000..822aa8b251
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompare/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompare:2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompare:3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompare:arrayed_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompare:arrayed_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompare:control_flow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompare:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel/cts.https.html
new file mode 100644
index 0000000000..58ea567f06
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:arrayed_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:arrayed_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:control_flow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleCompareLevel:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleGrad/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleGrad/cts.https.html
new file mode 100644
index 0000000000..c95da1bbee
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleGrad/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleGrad:sampled_array_3d_coords:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleLevel/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleLevel/cts.https.html
new file mode 100644
index 0000000000..a97d311adc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureSampleLevel/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:depth_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:depth_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:depth_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_array_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleLevel:sampled_array_3d_coords:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureStore/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureStore/cts.https.html
new file mode 100644
index 0000000000..97761fd754
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/textureStore/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureStore:store_1d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureStore:store_2d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureStore:store_3d_coords:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureStore:store_array_2d_coords:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/transpose/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/transpose/cts.https.html
new file mode 100644
index 0000000000..46834158d4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/transpose/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,transpose:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,transpose:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/trunc/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/trunc/cts.https.html
new file mode 100644
index 0000000000..21b1ff86b4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/trunc/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,trunc:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,trunc:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16float/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16float/cts.https.html
new file mode 100644
index 0000000000..4bec9d04b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16float/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,unpack2x16float:unpack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm/cts.https.html
new file mode 100644
index 0000000000..8b1c74ed91
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,unpack2x16snorm:unpack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm/cts.https.html
new file mode 100644
index 0000000000..6cb66b546a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,unpack2x16unorm:unpack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm/cts.https.html
new file mode 100644
index 0000000000..40ffec7056
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,unpack4x8snorm:unpack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm/cts.https.html
new file mode 100644
index 0000000000..2026a1af3a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,unpack4x8unorm:unpack:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/workgroupBarrier/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/workgroupBarrier/cts.https.html
new file mode 100644
index 0000000000..88532cf13f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/call/builtin/workgroupBarrier/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,workgroupBarrier:barrier:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,workgroupBarrier:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_arithmetic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_arithmetic/cts.https.html
new file mode 100644
index 0000000000..5f920cc191
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_arithmetic/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,af_arithmetic:negation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_assignment/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_assignment/cts.https.html
new file mode 100644
index 0000000000..482e825dac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/af_assignment/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,af_assignment:abstract:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,af_assignment:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,af_assignment:f32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_conversion/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_conversion/cts.https.html
new file mode 100644
index 0000000000..94a2d83296
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_conversion/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,bool_conversion:bool:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,bool_conversion:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,bool_conversion:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,bool_conversion:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,bool_conversion:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_logical/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_logical/cts.https.html
new file mode 100644
index 0000000000..676963ad0b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/bool_logical/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,bool_logical:negation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_arithmetic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_arithmetic/cts.https.html
new file mode 100644
index 0000000000..3083060820
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_arithmetic/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_arithmetic:negation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_conversion/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_conversion/cts.https.html
new file mode 100644
index 0000000000..b058c65787
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f16_conversion/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:bool:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:f16_mat:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:f32_mat:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f16_conversion:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_arithmetic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_arithmetic/cts.https.html
new file mode 100644
index 0000000000..ab2231a215
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_arithmetic/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_arithmetic:negation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_conversion/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_conversion/cts.https.html
new file mode 100644
index 0000000000..299223c36b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/f32_conversion/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:bool:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:f16_mat:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:f32_mat:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,f32_conversion:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_arithmetic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_arithmetic/cts.https.html
new file mode 100644
index 0000000000..fe5b52cd4d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_arithmetic/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_arithmetic:negation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_complement/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_complement/cts.https.html
new file mode 100644
index 0000000000..dae48cfadd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_complement/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_complement:i32_complement:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_conversion/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_conversion/cts.https.html
new file mode 100644
index 0000000000..2928999f71
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/i32_conversion/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_conversion:bool:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_conversion:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_conversion:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_conversion:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,i32_conversion:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_complement/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_complement/cts.https.html
new file mode 100644
index 0000000000..c1054a1f91
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_complement/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_complement:u32_complement:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_conversion/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_conversion/cts.https.html
new file mode 100644
index 0000000000..b9dfd9dbd2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/expression/unary/u32_conversion/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_conversion:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_conversion:bool:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_conversion:f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_conversion:f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_conversion:i32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,unary,u32_conversion:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/float_parse/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/float_parse/cts.https.html
new file mode 100644
index 0000000000..fc4627189f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/float_parse/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,float_parse:valid:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/call/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/call/cts.https.html
new file mode 100644
index 0000000000..17df849095
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/call/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,call:call_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,call:call_nested:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,call:call_repeated:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/complex/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/complex/cts.https.html
new file mode 100644
index 0000000000..e30a3e5656
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/complex/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,complex:continue_in_switch_in_for_loop:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/eval_order/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/eval_order/cts.https.html
new file mode 100644
index 0000000000..ddfff71e5d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/eval_order/cts.https.html
@@ -0,0 +1,71 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:1d_array_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:1d_array_compound_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:1d_array_constructor:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:1d_array_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:2d_array_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:2d_array_compound_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:2d_array_constructor:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:2d_array_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:array_index:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:array_index_lhs_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:array_index_lhs_member_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:array_index_via_ptrs:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:array_index_via_struct_members:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_chain:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_chain_C_C_C_R:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_chain_C_C_R_C:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_chain_C_R_C_C:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_chain_R_C_C_C:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_lhs_const:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_parenthesized_expr:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:binary_op_rhs_const:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:bitwise_and:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:bitwise_or:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:builtin_fn_args:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:logical_and:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:logical_or:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:matrix_index:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:matrix_index_via_ptr:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:nested_builtin_fn_args:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:nested_fn_args:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:nested_struct_constructor:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:nested_vec4_constructor:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:struct_constructor:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:user_fn_args:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,eval_order:vec4_constructor:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/for/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/for/cts.https.html
new file mode 100644
index 0000000000..bfe852e353
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/for/cts.https.html
@@ -0,0 +1,46 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_break:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_complex_condition:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_complex_continuing:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_complex_initalizer:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_condition:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_continue:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_continuing:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:for_initalizer:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:nested_for_break:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,for:nested_for_continue:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/if/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/if/cts.https.html
new file mode 100644
index 0000000000..02d166aa9c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/if/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,if:else_if:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,if:if_false:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,if:if_true:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,if:nested_if_else:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/loop/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/loop/cts.https.html
new file mode 100644
index 0000000000..c475971bbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/loop/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,loop:loop_break:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,loop:loop_continue:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,loop:loop_continuing_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,loop:nested_loops:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/phony/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/phony/cts.https.html
new file mode 100644
index 0000000000..6dab0c85f0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/phony/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,phony:phony_assign_call_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,phony:phony_assign_call_builtin:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,phony:phony_assign_call_must_use:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,phony:phony_assign_call_nested:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,phony:phony_assign_call_nested_must_use:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/return/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/return/cts.https.html
new file mode 100644
index 0000000000..e077e924fb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/return/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,return:return:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,return:return_conditional_false:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,return:return_conditional_true:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/switch/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/switch/cts.https.html
new file mode 100644
index 0000000000..5a985e70bb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/switch/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,switch:switch:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,switch:switch_default:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,switch:switch_default_only:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,switch:switch_multiple_case:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,switch:switch_multiple_case_default:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/while/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/while/cts.https.html
new file mode 100644
index 0000000000..4f5a053243
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/flow_control/while/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,while:while_basic:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,while:while_break:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,while:while_continue:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,while:while_nested_break:*'>
+<meta name=variant content='?q=webgpu:shader,execution,flow_control,while:while_nested_continue:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/adjacent/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/adjacent/cts.https.html
new file mode 100644
index 0000000000..5fe961ca6c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/adjacent/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,adjacent:f16:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/atomicity/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/atomicity/cts.https.html
new file mode 100644
index 0000000000..a05e5416ef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/atomicity/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,atomicity:atomicity:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/barrier/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/barrier/cts.https.html
new file mode 100644
index 0000000000..ae88877de1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/barrier/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,barrier:workgroup_barrier_load_store:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,barrier:workgroup_barrier_store_load:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,barrier:workgroup_barrier_store_store:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/coherence/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/coherence/cts.https.html
new file mode 100644
index 0000000000..bbe00a7611
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/coherence/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corr:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corw1:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corw2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:cowr:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:coww:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/weak/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/weak/cts.https.html
new file mode 100644
index 0000000000..737b2ebbdc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/memory_model/weak/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:2_plus_2_write:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:load_buffer:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:message_passing:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:read:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:store:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:store_buffer:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/padding/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/padding/cts.https.html
new file mode 100644
index 0000000000..8d53117f32
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/padding/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,padding:array_of_matCx3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:array_of_struct:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:array_of_vec3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:matCx3:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:struct_explicit:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:struct_implicit:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:struct_nested:*'>
+<meta name=variant content='?q=webgpu:shader,execution,padding:vec3:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access/cts.https.html
new file mode 100644
index 0000000000..63253b089d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,robust_access:linear_memory:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access_vertex/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access_vertex/cts.https.html
new file mode 100644
index 0000000000..4a3f01bb39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/robust_access_vertex/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,robust_access_vertex:vertex_buffer_access:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/compute_builtins/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/compute_builtins/cts.https.html
new file mode 100644
index 0000000000..e0c43c990c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/compute_builtins/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,shader_io,compute_builtins:inputs:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/shared_structs/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/shared_structs/cts.https.html
new file mode 100644
index 0000000000..bc14374b27
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shader_io/shared_structs/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,shader_io,shared_structs:shared_between_stages:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shader_io,shared_structs:shared_with_buffer:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shader_io,shared_structs:shared_with_non_entry_point_function:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shadow/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shadow/cts.https.html
new file mode 100644
index 0000000000..faf92f24b2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/shadow/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,shadow:builtin:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shadow:declaration:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shadow:for_loop:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shadow:if:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shadow:loop:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shadow:switch:*'>
+<meta name=variant content='?q=webgpu:shader,execution,shadow:while:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/statement/increment_decrement/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/statement/increment_decrement/cts.https.html
new file mode 100644
index 0000000000..72f3373bc6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/statement/increment_decrement/cts.https.html
@@ -0,0 +1,50 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:frexp_exp_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_i32_decrement_underflow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_i32_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_i32_increment_overflow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_u32_decrement:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_u32_decrement_underflow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_u32_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:scalar_u32_increment_overflow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:vec2_element_decrement:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:vec2_element_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:vec3_element_decrement:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:vec3_element_increment:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:vec4_element_decrement:*'>
+<meta name=variant content='?q=webgpu:shader,execution,statement,increment_decrement:vec4_element_increment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/zero_init/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/zero_init/cts.https.html
new file mode 100644
index 0000000000..b61ad4d7a1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/execution/zero_init/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,execution,zero_init:compute,zero_init:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/const_assert/const_assert/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/const_assert/const_assert/cts.https.html
new file mode 100644
index 0000000000..b2bae50bdc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/const_assert/const_assert/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_and_no_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_logical_or_no_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:constant_expression_no_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,const_assert,const_assert:evaluation_stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/const/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/const/cts.https.html
new file mode 100644
index 0000000000..d9cca95d49
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/const/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,decl,const:no_direct_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,const:no_indirect_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,const:no_indirect_recursion_via_array_size:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,const:no_indirect_recursion_via_struct_attribute:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/override/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/override/cts.https.html
new file mode 100644
index 0000000000..55c0091184
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/override/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,decl,override:no_direct_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,override:no_indirect_recursion:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/ptr_spelling/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/ptr_spelling/cts.https.html
new file mode 100644
index 0000000000..f5fa266b59
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/ptr_spelling/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:let_ptr_explicit_type_matches_var:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:let_ptr_reads:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:let_ptr_writes:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:ptr_address_space_never_uses_access_mode:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:ptr_bad_store_type:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:ptr_handle_space_invalid:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,ptr_spelling:ptr_not_instantiable:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/var_access_mode/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/var_access_mode/cts.https.html
new file mode 100644
index 0000000000..995cf12a46
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/decl/var_access_mode/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,decl,var_access_mode:explicit_access_mode:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,var_access_mode:implicit_access_mode:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,var_access_mode:read_access:*'>
+<meta name=variant content='?q=webgpu:shader,validation,decl,var_access_mode:write_access:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/access/vector/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/access/vector/cts.https.html
new file mode 100644
index 0000000000..4e15fa1a73
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/access/vector/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,access,vector:vector:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/binary/bitwise_shift/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/binary/bitwise_shift/cts.https.html
new file mode 100644
index 0000000000..1e096d540d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/binary/bitwise_shift/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_concrete:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,binary,bitwise_shift:shift_left_vec_size_mismatch:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_concrete:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,binary,bitwise_shift:shift_right_vec_size_mismatch:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/abs/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/abs/cts.https.html
new file mode 100644
index 0000000000..d772926886
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/abs/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,abs:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acos/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acos/cts.https.html
new file mode 100644
index 0000000000..13dce9fe12
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acos/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,acos:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,acos:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acosh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acosh/cts.https.html
new file mode 100644
index 0000000000..fe2aa41eec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/acosh/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,acosh:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,acosh:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asin/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asin/cts.https.html
new file mode 100644
index 0000000000..8d5305e83d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asin/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,asin:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,asin:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asinh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asinh/cts.https.html
new file mode 100644
index 0000000000..52d74d75c8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/asinh/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,asinh:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,asinh:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan/cts.https.html
new file mode 100644
index 0000000000..80823df40b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atan:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atan:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan2/cts.https.html
new file mode 100644
index 0000000000..6f6c70206d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atan2/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atan2:integer_argument_x:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atan2:integer_argument_y:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atan2:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atanh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atanh/cts.https.html
new file mode 100644
index 0000000000..0e2a65cf5e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atanh/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atanh:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atanh:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atomics/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atomics/cts.https.html
new file mode 100644
index 0000000000..9b82304aa6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/atomics/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,atomics:stage:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/bitcast/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/bitcast/cts.https.html
new file mode 100644
index 0000000000..caf7d17b45
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/bitcast/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:bad_const_to_f16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:bad_const_to_f32:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:bad_to_f16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:bad_to_vec3h:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:bad_type_constructible:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:bad_type_nonconstructible:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:valid_vec2h:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,bitcast:valid_vec4h:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/ceil/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/ceil/cts.https.html
new file mode 100644
index 0000000000..c257edaafe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/ceil/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,ceil:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,ceil:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/clamp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/clamp/cts.https.html
new file mode 100644
index 0000000000..0f357a2ed2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/clamp/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,clamp:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cos/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cos/cts.https.html
new file mode 100644
index 0000000000..4f6ead08ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cos/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,cos:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,cos:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cosh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cosh/cts.https.html
new file mode 100644
index 0000000000..97b4aef554
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/cosh/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,cosh:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,cosh:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/degrees/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/degrees/cts.https.html
new file mode 100644
index 0000000000..cf33579e4e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/degrees/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,degrees:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,degrees:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp/cts.https.html
new file mode 100644
index 0000000000..2dccb5e18f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,exp:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,exp:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp2/cts.https.html
new file mode 100644
index 0000000000..2ff4ac3d29
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/exp2/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,exp2:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,exp2:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/inverseSqrt/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/inverseSqrt/cts.https.html
new file mode 100644
index 0000000000..3b761ea97a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/inverseSqrt/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,inverseSqrt:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,inverseSqrt:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/length/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/length/cts.https.html
new file mode 100644
index 0000000000..46d4957160
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/length/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,length:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,length:scalar:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,length:vec2:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,length:vec3:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,length:vec4:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log/cts.https.html
new file mode 100644
index 0000000000..3e60dfb1f1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,log:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,log:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log2/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log2/cts.https.html
new file mode 100644
index 0000000000..f4e975a8e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/log2/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,log2:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,log2:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/modf/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/modf/cts.https.html
new file mode 100644
index 0000000000..72d82ec191
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/modf/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,modf:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,modf:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/radians/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/radians/cts.https.html
new file mode 100644
index 0000000000..99c0f5738b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/radians/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,radians:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,radians:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/round/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/round/cts.https.html
new file mode 100644
index 0000000000..cfc4493d42
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/round/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,round:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,round:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/saturate/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/saturate/cts.https.html
new file mode 100644
index 0000000000..66f6773b11
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/saturate/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,saturate:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,saturate:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sign/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sign/cts.https.html
new file mode 100644
index 0000000000..e7753a1acb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sign/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sign:unsigned_integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sign:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sin/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sin/cts.https.html
new file mode 100644
index 0000000000..2ca00f36a8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sin/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sin:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sin:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sinh/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sinh/cts.https.html
new file mode 100644
index 0000000000..df424272b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sinh/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sinh:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sinh:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sqrt/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sqrt/cts.https.html
new file mode 100644
index 0000000000..093fd2e319
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/sqrt/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sqrt:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,sqrt:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/tan/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/tan/cts.https.html
new file mode 100644
index 0000000000..3b99a17b78
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/expression/call/builtin/tan/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,tan:integer_argument:*'>
+<meta name=variant content='?q=webgpu:shader,validation,expression,call,builtin,tan:values:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/alias_analysis/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/alias_analysis/cts.https.html
new file mode 100644
index 0000000000..c0c2a2b5e2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/alias_analysis/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,functions,alias_analysis:aliasing_inside_function:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,alias_analysis:member_accessors:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,alias_analysis:one_pointer_one_module_scope:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,alias_analysis:same_pointer_read_and_write:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,alias_analysis:subcalls:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,alias_analysis:two_pointers:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/restrictions/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/restrictions/cts.https.html
new file mode 100644
index 0000000000..e4333302d9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/functions/restrictions/cts.https.html
@@ -0,0 +1,46 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:call_arg_types_match_params:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:entry_point_call_target:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:function_parameter_matching:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:function_parameter_types:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:function_return_types:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:no_direct_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:no_indirect_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:param_names_must_differ:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:param_number_matches_call:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:param_scope_is_function_body:*'>
+<meta name=variant content='?q=webgpu:shader,validation,functions,restrictions:vertex_returns_position:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/align/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/align/cts.https.html
new file mode 100644
index 0000000000..cf9816573f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/align/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,align:multi_align:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,align:parsing:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,align:placement:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,align:required_alignment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/attribute/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/attribute/cts.https.html
new file mode 100644
index 0000000000..2f65f42d39
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/attribute/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,attribute:expressions:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/binary_ops/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/binary_ops/cts.https.html
new file mode 100644
index 0000000000..2682fab4d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/binary_ops/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,binary_ops:all:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/blankspace/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/blankspace/cts.https.html
new file mode 100644
index 0000000000..35a97ecc0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/blankspace/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,blankspace:blankspace:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,blankspace:bom:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,blankspace:null_characters:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/break/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/break/cts.https.html
new file mode 100644
index 0000000000..a11f06a59e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/break/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,break:placement:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/builtin/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/builtin/cts.https.html
new file mode 100644
index 0000000000..78db5a6ce0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/builtin/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,builtin:parse:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,builtin:placement:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/comments/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/comments/cts.https.html
new file mode 100644
index 0000000000..61988caac3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/comments/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,comments:comments:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,comments:line_comment_eof:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,comments:line_comment_terminators:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,comments:unterminated_block_comment:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const/cts.https.html
new file mode 100644
index 0000000000..9eb59a1626
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,const:placement:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const_assert/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const_assert/cts.https.html
new file mode 100644
index 0000000000..c50171e363
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/const_assert/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,const_assert:parse:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/diagnostic/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/diagnostic/cts.https.html
new file mode 100644
index 0000000000..03f434cfe0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/diagnostic/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:conflicting_attribute_different_location:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:conflicting_attribute_same_location:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:conflicting_directive:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:invalid_locations:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:invalid_severity:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:valid_locations:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:valid_params:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,diagnostic:warning_unknown_rule:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/discard/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/discard/cts.https.html
new file mode 100644
index 0000000000..01d3f1c487
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/discard/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,discard:placement:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/enable/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/enable/cts.https.html
new file mode 100644
index 0000000000..75f9d410be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/enable/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,enable:enable:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/identifiers/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/identifiers/cts.https.html
new file mode 100644
index 0000000000..ece6d30667
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/identifiers/cts.https.html
@@ -0,0 +1,46 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:alias_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:function_const_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:function_let_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:function_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:function_param_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:function_var_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:module_const_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:module_var_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:non_normalized:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:override_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,identifiers:struct_name:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/literal/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/literal/cts.https.html
new file mode 100644
index 0000000000..d029052c5c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/literal/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:abstract_int:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:bools:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:f16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:f32:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:i32:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,literal:u32:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/must_use/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/must_use/cts.https.html
new file mode 100644
index 0000000000..6e413a3b04
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/must_use/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,must_use:builtin_must_use:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,must_use:builtin_no_must_use:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,must_use:call:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,must_use:declaration:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/pipeline_stage/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/pipeline_stage/cts.https.html
new file mode 100644
index 0000000000..bc77f0bd9d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/pipeline_stage/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:compute_parsing:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:duplicate_compute_on_function:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:duplicate_fragment_on_function:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:duplicate_vertex_on_function:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:fragment_parsing:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:multiple_entry_points:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:placement:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,pipeline_stage:vertex_parsing:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/semicolon/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/semicolon/cts.https.html
new file mode 100644
index 0000000000..0d510c72b4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/semicolon/cts.https.html
@@ -0,0 +1,74 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_assignment:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_call:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_case:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_case_break:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_compound_statement:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_continuing:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_default_case:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_default_case_break:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_discard:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_enable:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_fn_const_assert:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_fn_const_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_fn_var_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_for:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_for_break:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_func_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_if:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_if_else:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_let_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_loop:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_loop_break:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_loop_break_if:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_loop_continue:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_member:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_module_const_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_module_var_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_return:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_struct_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_switch:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_type_alias_decl:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_while:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_while_break:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:after_while_continue:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:compound_statement_multiple:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:compound_statement_single:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:function_body_multiple:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:function_body_single:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:module_scope_multiple:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,semicolon:module_scope_single:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/source/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/source/cts.https.html
new file mode 100644
index 0000000000..31a38a9060
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/source/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,source:empty:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,source:invalid_source:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,source:valid_source:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/unary_ops/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/unary_ops/cts.https.html
new file mode 100644
index 0000000000..bd9c61f29b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/unary_ops/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,unary_ops:all:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/var_and_let/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/var_and_let/cts.https.html
new file mode 100644
index 0000000000..69ed0ccc57
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/parse/var_and_let/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,parse,var_and_let:initializer_type:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,var_and_let:var_access_mode_bad_other_template_contents:*'>
+<meta name=variant content='?q=webgpu:shader,validation,parse,var_and_let:var_access_mode_bad_template_delim:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/binding/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/binding/cts.https.html
new file mode 100644
index 0000000000..9dde54b51b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/binding/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,binding:binding:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,binding:binding_f16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,binding:binding_without_group:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/builtins/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/builtins/cts.https.html
new file mode 100644
index 0000000000..81d116a94e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/builtins/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,builtins:duplicates:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,builtins:missing_vertex_position:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,builtins:nesting:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,builtins:reuse_builtin_name:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,builtins:stage_inout:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,builtins:type:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/entry_point/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/entry_point/cts.https.html
new file mode 100644
index 0000000000..0d1ba131db
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/entry_point/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_param:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_param_struct:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_return_type:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,entry_point:missing_attribute_on_return_type_struct:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,entry_point:no_entry_point_provided:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group/cts.https.html
new file mode 100644
index 0000000000..19974f2dcf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group:group:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group:group_f16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group:group_without_binding:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group_and_binding/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group_and_binding/cts.https.html
new file mode 100644
index 0000000000..338e59a06e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/group_and_binding/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:binding_attributes:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:different_entry_points:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:function_scope:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:function_scope_texture:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:private_function_scope:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:private_module_scope:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,group_and_binding:single_entry_point:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/id/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/id/cts.https.html
new file mode 100644
index 0000000000..50f46a31c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/id/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,id:id:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,id:id_fp16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,id:id_in_function:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,id:id_non_override:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,id:id_struct_member:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/interpolate/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/interpolate/cts.https.html
new file mode 100644
index 0000000000..1352de7557
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/interpolate/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,interpolate:duplicate:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,interpolate:integral_types:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,interpolate:interpolation_validation:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,interpolate:require_location:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,interpolate:type_and_sampling:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/invariant/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/invariant/cts.https.html
new file mode 100644
index 0000000000..acc3f06baa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/invariant/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,invariant:not_valid_on_user_defined_io:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,invariant:parsing:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,invariant:valid_only_with_vertex_position_builtin:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/locations/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/locations/cts.https.html
new file mode 100644
index 0000000000..98ad686ed8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/locations/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,locations:duplicates:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,locations:location_fp16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,locations:nesting:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,locations:stage_inout:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,locations:type:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,locations:validation:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/size/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/size/cts.https.html
new file mode 100644
index 0000000000..d9bfbede3a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/size/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,size:size:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,size:size_fp16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,size:size_non_struct:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/workgroup_size/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/workgroup_size/cts.https.html
new file mode 100644
index 0000000000..4538162971
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/shader_io/workgroup_size/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_const:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_fp16:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_fragment_shader:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_function:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_var:*'>
+<meta name=variant content='?q=webgpu:shader,validation,shader_io,workgroup_size:workgroup_size_vertex_shader:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/alias/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/alias/cts.https.html
new file mode 100644
index 0000000000..08e7d8d4f7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/alias/cts.https.html
@@ -0,0 +1,45 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_direct_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_array_element:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_array_size:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_atomic:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_matrix_element:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_ptr_store_type:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_attribute:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_struct_member:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,alias:no_indirect_recursion_via_vector_element:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/struct/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/struct/cts.https.html
new file mode 100644
index 0000000000..bc8e8f3c3f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/struct/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,types,struct:no_direct_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,struct:no_indirect_recursion:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_element:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,struct:no_indirect_recursion_via_array_size:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,struct:no_indirect_recursion_via_struct_attribute:*'>
+<meta name=variant content='?q=webgpu:shader,validation,types,struct:no_indirect_recursion_via_struct_member_nested_in_alias:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/vector/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/vector/cts.https.html
new file mode 100644
index 0000000000..08d18d0953
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/types/vector/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,types,vector:vector:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/uniformity/uniformity/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/uniformity/uniformity/cts.https.html
new file mode 100644
index 0000000000..5886032ff4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/shader/validation/uniformity/uniformity/cts.https.html
@@ -0,0 +1,45 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:basics:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:binary_expressions:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:compute_builtin_values:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:fragment_builtin_values:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:function_pointer_parameters:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:function_variables:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:functions:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:pointers:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:short_circuit_expressions:*'>
+<meta name=variant content='?q=webgpu:shader,validation,uniformity,uniformity:unary_expressions:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texel_data/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texel_data/cts.https.html
new file mode 100644
index 0000000000..4e3683cdf6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texel_data/cts.https.html
@@ -0,0 +1,41 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:util,texture,texel_data:float_texel_data_in_shader:*'>
+<meta name=variant content='?q=webgpu:util,texture,texel_data:sint_texel_data_in_shader:*'>
+<meta name=variant content='?q=webgpu:util,texture,texel_data:snorm_texel_data_in_shader:*'>
+<meta name=variant content='?q=webgpu:util,texture,texel_data:ufloat_texel_data_in_shader:*'>
+<meta name=variant content='?q=webgpu:util,texture,texel_data:uint_texel_data_in_shader:*'>
+<meta name=variant content='?q=webgpu:util,texture,texel_data:unorm_texel_data_in_shader:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texture_ok/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texture_ok/cts.https.html
new file mode 100644
index 0000000000..92c3dc0f83
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/util/texture/texture_ok/cts.https.html
@@ -0,0 +1,38 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:util,texture,texture_ok:float32:*'>
+<meta name=variant content='?q=webgpu:util,texture,texture_ok:norm:*'>
+<meta name=variant content='?q=webgpu:util,texture,texture_ok:snorm_min:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/configure/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/configure/cts.https.html
new file mode 100644
index 0000000000..8d709be19f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/configure/cts.https.html
@@ -0,0 +1,43 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:alpha_mode:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:defaults:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:device:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:format:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:size_zero_after_configure:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:size_zero_before_configure:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:usage:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,configure:viewFormats:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/context_creation/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/context_creation/cts.https.html
new file mode 100644
index 0000000000..4c87e1aa93
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/context_creation/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,canvas,context_creation:return_type:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getCurrentTexture/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getCurrentTexture/cts.https.html
new file mode 100644
index 0000000000..4b9a72dbbb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getCurrentTexture/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,canvas,getCurrentTexture:configured:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,getCurrentTexture:expiry:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,getCurrentTexture:multiple_frames:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,getCurrentTexture:resize:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,getCurrentTexture:single_frames:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getPreferredCanvasFormat/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getPreferredCanvasFormat/cts.https.html
new file mode 100644
index 0000000000..a02a44d9d9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/getPreferredCanvasFormat/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,canvas,getPreferredCanvasFormat:value:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/readbackFromWebGPUCanvas/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/readbackFromWebGPUCanvas/cts.https.html
new file mode 100644
index 0000000000..9a45ed60a2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/canvas/readbackFromWebGPUCanvas/cts.https.html
@@ -0,0 +1,42 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:drawTo2DCanvas:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:offscreenCanvas,snapshot:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,snapshot:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,uploadToWebGL:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_huge_size:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_unconfigured_nonzero_size:*'>
+<meta name=variant content='?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:transferToImageBitmap_zero_size:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/cts.https.html
new file mode 100644
index 0000000000..f84d464bb0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageBitmap/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,ImageBitmap:copy_subrect_from_2D_Canvas:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,ImageBitmap:copy_subrect_from_ImageData:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,ImageBitmap:from_ImageData:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,ImageBitmap:from_canvas:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageData/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageData/cts.https.html
new file mode 100644
index 0000000000..60675e6dbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/ImageData/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,ImageData:copy_subrect_from_ImageData:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,ImageData:from_ImageData:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/canvas/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/canvas/cts.https.html
new file mode 100644
index 0000000000..f5cd760cac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/canvas/cts.https.html
@@ -0,0 +1,40 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,canvas:color_space_conversion:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_2d_context_canvas:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_bitmaprenderer_context_canvas:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gl_context_canvas:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gpu_context_canvas:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/image/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/image/cts.https.html
new file mode 100644
index 0000000000..0d2127a1e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/image/cts.https.html
@@ -0,0 +1,37 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,image:copy_subrect_from_2D_Canvas:*'>
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,image:from_image:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/video/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/video/cts.https.html
new file mode 100644
index 0000000000..b7f0093813
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/copyToTexture/video/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,copyToTexture,video:copy_from_video:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/external_texture/video/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/external_texture/video/cts.https.html
new file mode 100644
index 0000000000..94d61c32ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/external_texture/video/cts.https.html
@@ -0,0 +1,39 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,external_texture,video:importExternalTexture,compute:*'>
+<meta name=variant content='?q=webgpu:web_platform,external_texture,video:importExternalTexture,sample:*'>
+<meta name=variant content='?q=webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithRotationMetadata:*'>
+<meta name=variant content='?q=webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithVideoFrameWithVisibleRectParam:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/worker/worker/cts.https.html b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/worker/worker/cts.https.html
new file mode 100644
index 0000000000..08f7bfbacd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/cts/webgpu/web_platform/worker/worker/cts.https.html
@@ -0,0 +1,36 @@
+<!-- AUTO-GENERATED - DO NOT EDIT. See WebGPU CTS: tools/gen_wpt_cts_html. -->
+<!--
+ This test suite is built from the TypeScript sources at:
+ https://github.com/gpuweb/cts
+
+ If you are debugging WebGPU conformance tests, it's highly recommended that
+ you use the standalone interactive runner in that repository, which
+ provides tools for easier debugging and editing (source maps, debug
+ logging, warn/skip functionality, etc.)
+
+ NOTE:
+ The WPT version of this file is generated with *one variant per test spec
+ file*. If your harness needs more fine-grained suppressions, you'll need to
+ generate your own variants list from your suppression list.
+ See `tools/gen_wpt_cts_html` to do this.
+
+ When run under browser CI, the original cts.https.html should be skipped, and
+ this alternate version should be run instead, under a non-exported WPT test
+ directory (e.g. Chromium's wpt_internal).
+-->
+
+<!doctype html>
+<title>WebGPU CTS</title>
+<meta charset=utf-8>
+<meta name="timeout" content="long"> <!-- TODO: narrow to only where it's needed, see https://bugzilla.mozilla.org/show_bug.cgi?id=1850537 -->
+<link rel=help href='https://gpuweb.github.io/gpuweb/'>
+
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ const loadWebGPUExpectations = undefined;
+ const shouldWebGPUCTSFailOnWarnings = undefined;
+</script>
+<script type=module src=/_mozilla/webgpu/common/runtime/wpt.js></script>
+
+<meta name=variant content='?q=webgpu:web_platform,worker,worker:worker:*'>
diff --git a/testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.d.js b/testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.d.js
new file mode 100644
index 0000000000..138c3378a5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.d.js
@@ -0,0 +1,470 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * A typed array of 16-bit float values. The contents are initialized to 0. If the requested number
+ * of bytes could not be allocated an exception is raised.
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Returns `true` if the value is a Float16Array instance.
+ * @since v3.4.0
+ */
+
+
+/**
+ * Returns `true` if the value is a type of TypedArray instance that contains Float16Array.
+ * @since v3.6.0
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Gets the Float16 value at the specified byte offset from the start of the view. There is
+ * no alignment constraint; multi-byte values may be fetched from any offset.
+ * @param byteOffset The place in the buffer at which the value should be retrieved.
+ * @param littleEndian If false or undefined, a big-endian value should be read,
+ * otherwise a little-endian value should be read.
+ */
+
+
+
+
+
+
+/**
+ * Stores an Float16 value at the specified byte offset from the start of the view.
+ * @param byteOffset The place in the buffer at which the value should be set.
+ * @param value The value to set.
+ * @param littleEndian If false or undefined, a big-endian value should be written,
+ * otherwise a little-endian value should be written.
+ */
+
+
+
+
+
+
+
+/**
+ * Returns the nearest half-precision float representation of a number.
+ * @param x A numeric expression.
+ */export {}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.js b/testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.js
new file mode 100644
index 0000000000..42543cdea1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/external/petamoriken/float16/float16.js
@@ -0,0 +1,1228 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /*! @petamoriken/float16 v3.6.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";
+const THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY =
+"This constructor is not a subclass of Float16Array";
+const THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT =
+"The constructor property value is not an object";
+const SPECIES_CONSTRUCTOR_DIDNT_RETURN_TYPEDARRAY_OBJECT =
+"Species constructor didn't return TypedArray object";
+const DERIVED_CONSTRUCTOR_CREATED_TYPEDARRAY_OBJECT_WHICH_WAS_TOO_SMALL_LENGTH =
+"Derived constructor created TypedArray object which was too small length";
+const ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER =
+"Attempting to access detached ArrayBuffer";
+const CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT =
+"Cannot convert undefined or null to object";
+const CANNOT_MIX_BIGINT_AND_OTHER_TYPES =
+"Cannot mix BigInt and other types, use explicit conversions";
+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 OFFSET_IS_OUT_OF_BOUNDS = "Offset is out of bounds";
+
+function uncurryThis(target) {
+ return (thisArg, ...args) => {
+ return ReflectApply(target, thisArg, args);
+ };
+}
+function uncurryThisGetter(target, key) {
+ return uncurryThis(
+ ReflectGetOwnPropertyDescriptor(
+ target,
+ key
+ ).get
+ );
+}
+const {
+ apply: ReflectApply,
+ construct: ReflectConstruct,
+ defineProperty: ReflectDefineProperty,
+ get: ReflectGet,
+ getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
+ getPrototypeOf: ReflectGetPrototypeOf,
+ has: ReflectHas,
+ ownKeys: ReflectOwnKeys,
+ set: ReflectSet,
+ setPrototypeOf: ReflectSetPrototypeOf
+} = Reflect;
+const NativeProxy = Proxy;
+const {
+ MAX_SAFE_INTEGER: MAX_SAFE_INTEGER,
+ isFinite: NumberIsFinite,
+ isNaN: NumberIsNaN
+} = Number;
+const {
+ iterator: SymbolIterator,
+ species: SymbolSpecies,
+ toStringTag: SymbolToStringTag,
+ for: SymbolFor
+} = Symbol;
+const NativeObject = Object;
+const {
+ create: ObjectCreate,
+ defineProperty: ObjectDefineProperty,
+ freeze: ObjectFreeze,
+ is: ObjectIs
+} = NativeObject;
+const ObjectPrototype = NativeObject.prototype;
+const ObjectPrototype__lookupGetter__ = ObjectPrototype.__lookupGetter__ ?
+uncurryThis(ObjectPrototype.__lookupGetter__) :
+(object, key) => {
+ if (object == null) {
+ throw NativeTypeError(
+ CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
+ );
+ }
+ let target = NativeObject(object);
+ do {
+ const descriptor = ReflectGetOwnPropertyDescriptor(target, key);
+ if (descriptor !== undefined) {
+ if (ObjectHasOwn(descriptor, "get")) {
+ return descriptor.get;
+ }
+ return;
+ }
+ } while ((target = ReflectGetPrototypeOf(target)) !== null);
+};
+const ObjectHasOwn = NativeObject.hasOwn ||
+uncurryThis(ObjectPrototype.hasOwnProperty);
+const NativeArray = Array;
+const ArrayIsArray = NativeArray.isArray;
+const ArrayPrototype = NativeArray.prototype;
+const ArrayPrototypeJoin = uncurryThis(ArrayPrototype.join);
+const ArrayPrototypePush = uncurryThis(ArrayPrototype.push);
+const ArrayPrototypeToLocaleString = uncurryThis(
+ ArrayPrototype.toLocaleString
+);
+const NativeArrayPrototypeSymbolIterator = ArrayPrototype[SymbolIterator];
+const ArrayPrototypeSymbolIterator = uncurryThis(NativeArrayPrototypeSymbolIterator);
+const MathTrunc = Math.trunc;
+const NativeArrayBuffer = ArrayBuffer;
+const ArrayBufferIsView = NativeArrayBuffer.isView;
+const ArrayBufferPrototype = NativeArrayBuffer.prototype;
+const ArrayBufferPrototypeSlice = uncurryThis(ArrayBufferPrototype.slice);
+const ArrayBufferPrototypeGetByteLength = uncurryThisGetter(ArrayBufferPrototype, "byteLength");
+const NativeSharedArrayBuffer = typeof SharedArrayBuffer !== "undefined" ? SharedArrayBuffer : null;
+const SharedArrayBufferPrototypeGetByteLength = NativeSharedArrayBuffer &&
+uncurryThisGetter(NativeSharedArrayBuffer.prototype, "byteLength");
+const TypedArray = ReflectGetPrototypeOf(Uint8Array);
+const TypedArrayFrom = TypedArray.from;
+const TypedArrayPrototype = TypedArray.prototype;
+const NativeTypedArrayPrototypeSymbolIterator = TypedArrayPrototype[SymbolIterator];
+const TypedArrayPrototypeKeys = uncurryThis(TypedArrayPrototype.keys);
+const TypedArrayPrototypeValues = uncurryThis(
+ TypedArrayPrototype.values
+);
+const TypedArrayPrototypeEntries = uncurryThis(
+ TypedArrayPrototype.entries
+);
+const TypedArrayPrototypeSet = uncurryThis(TypedArrayPrototype.set);
+const TypedArrayPrototypeReverse = uncurryThis(
+ TypedArrayPrototype.reverse
+);
+const TypedArrayPrototypeFill = uncurryThis(TypedArrayPrototype.fill);
+const TypedArrayPrototypeCopyWithin = uncurryThis(
+ TypedArrayPrototype.copyWithin
+);
+const TypedArrayPrototypeSort = uncurryThis(TypedArrayPrototype.sort);
+const TypedArrayPrototypeSlice = uncurryThis(TypedArrayPrototype.slice);
+const TypedArrayPrototypeSubarray = uncurryThis(
+ TypedArrayPrototype.subarray
+);
+const TypedArrayPrototypeGetBuffer = uncurryThisGetter(
+ TypedArrayPrototype,
+ "buffer"
+);
+const TypedArrayPrototypeGetByteOffset = uncurryThisGetter(
+ TypedArrayPrototype,
+ "byteOffset"
+);
+const TypedArrayPrototypeGetLength = uncurryThisGetter(
+ TypedArrayPrototype,
+ "length"
+);
+const TypedArrayPrototypeGetSymbolToStringTag = uncurryThisGetter(
+ TypedArrayPrototype,
+ SymbolToStringTag
+);
+const NativeUint16Array = Uint16Array;
+const Uint16ArrayFrom = (...args) => {
+ return ReflectApply(TypedArrayFrom, NativeUint16Array, args);
+};
+const NativeUint32Array = Uint32Array;
+const NativeFloat32Array = Float32Array;
+const ArrayIteratorPrototype = ReflectGetPrototypeOf([][SymbolIterator]());
+const ArrayIteratorPrototypeNext = uncurryThis(ArrayIteratorPrototype.next);
+const GeneratorPrototypeNext = uncurryThis(function* () {}().next);
+const IteratorPrototype = ReflectGetPrototypeOf(ArrayIteratorPrototype);
+const DataViewPrototype = DataView.prototype;
+const DataViewPrototypeGetUint16 = uncurryThis(
+ DataViewPrototype.getUint16
+);
+const DataViewPrototypeSetUint16 = uncurryThis(
+ DataViewPrototype.setUint16
+);
+const NativeTypeError = TypeError;
+const NativeRangeError = RangeError;
+const NativeWeakSet = WeakSet;
+const WeakSetPrototype = NativeWeakSet.prototype;
+const WeakSetPrototypeAdd = uncurryThis(WeakSetPrototype.add);
+const WeakSetPrototypeHas = uncurryThis(WeakSetPrototype.has);
+const NativeWeakMap = WeakMap;
+const WeakMapPrototype = NativeWeakMap.prototype;
+const WeakMapPrototypeGet = uncurryThis(WeakMapPrototype.get);
+const WeakMapPrototypeHas = uncurryThis(WeakMapPrototype.has);
+const WeakMapPrototypeSet = uncurryThis(WeakMapPrototype.set);
+
+const arrayIterators = new NativeWeakMap();
+const SafeIteratorPrototype = ObjectCreate(null, {
+ next: {
+ value: function next() {
+ const arrayIterator = WeakMapPrototypeGet(arrayIterators, this);
+ return ArrayIteratorPrototypeNext(arrayIterator);
+ }
+ },
+ [SymbolIterator]: {
+ value: function values() {
+ return this;
+ }
+ }
+});
+function safeIfNeeded(array) {
+ if (array[SymbolIterator] === NativeArrayPrototypeSymbolIterator) {
+ return array;
+ }
+ const safe = ObjectCreate(SafeIteratorPrototype);
+ WeakMapPrototypeSet(arrayIterators, safe, ArrayPrototypeSymbolIterator(array));
+ return safe;
+}
+const generators = new NativeWeakMap();
+const DummyArrayIteratorPrototype = ObjectCreate(IteratorPrototype, {
+ next: {
+ value: function next() {
+ const generator = WeakMapPrototypeGet(generators, this);
+ return GeneratorPrototypeNext(generator);
+ },
+ writable: true,
+ configurable: true
+ }
+});
+for (const key of ReflectOwnKeys(ArrayIteratorPrototype)) {
+ if (key === "next") {
+ continue;
+ }
+ ObjectDefineProperty(DummyArrayIteratorPrototype, key, ReflectGetOwnPropertyDescriptor(ArrayIteratorPrototype, key));
+}
+function wrap(generator) {
+ const dummy = ObjectCreate(DummyArrayIteratorPrototype);
+ WeakMapPrototypeSet(generators, dummy, generator);
+ return dummy;
+}
+
+function isObject(value) {
+ return value !== null && typeof value === "object" ||
+ typeof value === "function";
+}
+function isObjectLike(value) {
+ return value !== null && typeof value === "object";
+}
+function isNativeTypedArray(value) {
+ return TypedArrayPrototypeGetSymbolToStringTag(value) !== undefined;
+}
+function isNativeBigIntTypedArray(value) {
+ const typedArrayName = TypedArrayPrototypeGetSymbolToStringTag(value);
+ return typedArrayName === "BigInt64Array" ||
+ typedArrayName === "BigUint64Array";
+}
+function isArrayBuffer(value) {
+ try {
+ ArrayBufferPrototypeGetByteLength(value);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+function isSharedArrayBuffer(value) {
+ if (NativeSharedArrayBuffer === null) {
+ return false;
+ }
+ try {
+ SharedArrayBufferPrototypeGetByteLength(value);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+function isOrdinaryArray(value) {
+ if (!ArrayIsArray(value)) {
+ return false;
+ }
+ if (value[SymbolIterator] === NativeArrayPrototypeSymbolIterator) {
+ return true;
+ }
+ const iterator = value[SymbolIterator]();
+ return iterator[SymbolToStringTag] === "Array Iterator";
+}
+function isOrdinaryNativeTypedArray(value) {
+ if (!isNativeTypedArray(value)) {
+ return false;
+ }
+ if (value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator) {
+ return true;
+ }
+ const iterator = value[SymbolIterator]();
+ return iterator[SymbolToStringTag] === "Array Iterator";
+}
+function isCanonicalIntegerIndexString(value) {
+ if (typeof value !== "string") {
+ return false;
+ }
+ const number = +value;
+ if (value !== number + "") {
+ return false;
+ }
+ if (!NumberIsFinite(number)) {
+ return false;
+ }
+ return number === MathTrunc(number);
+}
+
+const brand = SymbolFor("__Float16Array__");
+function hasFloat16ArrayBrand(target) {
+ if (!isObjectLike(target)) {
+ return false;
+ }
+ const prototype = ReflectGetPrototypeOf(target);
+ if (!isObjectLike(prototype)) {
+ return false;
+ }
+ const constructor = prototype.constructor;
+ if (constructor === undefined) {
+ return false;
+ }
+ if (!isObject(constructor)) {
+ throw NativeTypeError(THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT);
+ }
+ return ReflectHas(constructor, brand);
+}
+
+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);
+for (let i = 0; i < 256; ++i) {
+ const e = i - 127;
+ if (e < -27) {
+ baseTable[i] = 0x0000;
+ baseTable[i | 0x100] = 0x8000;
+ shiftTable[i] = 24;
+ shiftTable[i | 0x100] = 24;
+ } else if (e < -14) {
+ baseTable[i] = 0x0400 >> -e - 14;
+ baseTable[i | 0x100] = 0x0400 >> -e - 14 | 0x8000;
+ shiftTable[i] = -e - 1;
+ shiftTable[i | 0x100] = -e - 1;
+ } else if (e <= 15) {
+ baseTable[i] = e + 15 << 10;
+ baseTable[i | 0x100] = e + 15 << 10 | 0x8000;
+ shiftTable[i] = 13;
+ shiftTable[i | 0x100] = 13;
+ } else if (e < 128) {
+ baseTable[i] = 0x7c00;
+ baseTable[i | 0x100] = 0xfc00;
+ shiftTable[i] = 24;
+ shiftTable[i | 0x100] = 24;
+ } else {
+ baseTable[i] = 0x7c00;
+ baseTable[i | 0x100] = 0xfc00;
+ shiftTable[i] = 13;
+ shiftTable[i | 0x100] = 13;
+ }
+}
+function roundToFloat16Bits(num) {
+ floatView[0] = 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) {
+ m <<= 1;
+ e -= 0x00800000;
+ }
+ m &= ~0x00800000;
+ e += 0x38800000;
+ mantissaTable[i] = m | e;
+}
+for (let i = 1024; i < 2048; ++i) {
+ mantissaTable[i] = 0x38000000 + (i - 1024 << 13);
+}
+for (let i = 1; i < 31; ++i) {
+ exponentTable[i] = i << 23;
+}
+exponentTable[31] = 0x47800000;
+exponentTable[32] = 0x80000000;
+for (let i = 33; i < 63; ++i) {
+ exponentTable[i] = 0x80000000 + (i - 32 << 23);
+}
+exponentTable[63] = 0xc7800000;
+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];
+ return floatView[0];
+}
+
+function ToIntegerOrInfinity(target) {
+ const number = +target;
+ if (NumberIsNaN(number) || number === 0) {
+ return 0;
+ }
+ return MathTrunc(number);
+}
+function ToLength(target) {
+ const length = ToIntegerOrInfinity(target);
+ if (length < 0) {
+ return 0;
+ }
+ return length < MAX_SAFE_INTEGER ?
+ length :
+ MAX_SAFE_INTEGER;
+}
+function SpeciesConstructor(target, defaultConstructor) {
+ if (!isObject(target)) {
+ throw NativeTypeError(THIS_IS_NOT_AN_OBJECT);
+ }
+ const constructor = target.constructor;
+ if (constructor === undefined) {
+ return defaultConstructor;
+ }
+ if (!isObject(constructor)) {
+ throw NativeTypeError(THE_CONSTRUCTOR_PROPERTY_VALUE_IS_NOT_AN_OBJECT);
+ }
+ const species = constructor[SymbolSpecies];
+ if (species == null) {
+ return defaultConstructor;
+ }
+ return species;
+}
+function IsDetachedBuffer(buffer) {
+ if (isSharedArrayBuffer(buffer)) {
+ return false;
+ }
+ try {
+ ArrayBufferPrototypeSlice(buffer, 0, 0);
+ return false;
+ } catch (e) {}
+ return true;
+}
+function defaultCompare(x, y) {
+ const isXNaN = NumberIsNaN(x);
+ const isYNaN = NumberIsNaN(y);
+ if (isXNaN && isYNaN) {
+ return 0;
+ }
+ if (isXNaN) {
+ return 1;
+ }
+ if (isYNaN) {
+ return -1;
+ }
+ if (x < y) {
+ return -1;
+ }
+ if (x > y) {
+ return 1;
+ }
+ if (x === 0 && y === 0) {
+ const isXPlusZero = ObjectIs(x, 0);
+ const isYPlusZero = ObjectIs(y, 0);
+ if (!isXPlusZero && isYPlusZero) {
+ return -1;
+ }
+ if (isXPlusZero && !isYPlusZero) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+const BYTES_PER_ELEMENT = 2;
+const float16bitsArrays = new NativeWeakMap();
+function isFloat16Array(target) {
+ return WeakMapPrototypeHas(float16bitsArrays, target) ||
+ !ArrayBufferIsView(target) && hasFloat16ArrayBrand(target);
+}
+function assertFloat16Array(target) {
+ if (!isFloat16Array(target)) {
+ throw NativeTypeError(THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT);
+ }
+}
+function assertSpeciesTypedArray(target, count) {
+ const isTargetFloat16Array = isFloat16Array(target);
+ const isTargetTypedArray = isNativeTypedArray(target);
+ if (!isTargetFloat16Array && !isTargetTypedArray) {
+ throw NativeTypeError(SPECIES_CONSTRUCTOR_DIDNT_RETURN_TYPEDARRAY_OBJECT);
+ }
+ if (typeof count === "number") {
+ let length;
+ if (isTargetFloat16Array) {
+ const float16bitsArray = getFloat16BitsArray(target);
+ length = TypedArrayPrototypeGetLength(float16bitsArray);
+ } else {
+ length = TypedArrayPrototypeGetLength(target);
+ }
+ if (length < count) {
+ throw NativeTypeError(
+ DERIVED_CONSTRUCTOR_CREATED_TYPEDARRAY_OBJECT_WHICH_WAS_TOO_SMALL_LENGTH
+ );
+ }
+ }
+ if (isNativeBigIntTypedArray(target)) {
+ throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
+ }
+}
+function getFloat16BitsArray(float16) {
+ const float16bitsArray = WeakMapPrototypeGet(float16bitsArrays, float16);
+ if (float16bitsArray !== undefined) {
+ const buffer = TypedArrayPrototypeGetBuffer(float16bitsArray);
+ if (IsDetachedBuffer(buffer)) {
+ throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
+ }
+ return float16bitsArray;
+ }
+ const buffer = float16.buffer;
+ if (IsDetachedBuffer(buffer)) {
+ throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
+ }
+ const cloned = ReflectConstruct(Float16Array, [
+ buffer,
+ float16.byteOffset,
+ float16.length],
+ float16.constructor);
+ return WeakMapPrototypeGet(float16bitsArrays, cloned);
+}
+function copyToArray(float16bitsArray) {
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const array = [];
+ for (let i = 0; i < length; ++i) {
+ array[i] = convertToNumber(float16bitsArray[i]);
+ }
+ return array;
+}
+const TypedArrayPrototypeGetters = new NativeWeakSet();
+for (const key of ReflectOwnKeys(TypedArrayPrototype)) {
+ if (key === SymbolToStringTag) {
+ continue;
+ }
+ const descriptor = ReflectGetOwnPropertyDescriptor(TypedArrayPrototype, key);
+ if (ObjectHasOwn(descriptor, "get") && typeof descriptor.get === "function") {
+ WeakSetPrototypeAdd(TypedArrayPrototypeGetters, descriptor.get);
+ }
+}
+const handler = ObjectFreeze({
+ get(target, key, receiver) {
+ if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
+ return convertToNumber(ReflectGet(target, key));
+ }
+ if (WeakSetPrototypeHas(TypedArrayPrototypeGetters, ObjectPrototype__lookupGetter__(target, key))) {
+ return ReflectGet(target, key);
+ }
+ return ReflectGet(target, key, receiver);
+ },
+ set(target, key, value, receiver) {
+ if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
+ return ReflectSet(target, key, roundToFloat16Bits(value));
+ }
+ return ReflectSet(target, key, value, receiver);
+ },
+ getOwnPropertyDescriptor(target, key) {
+ if (isCanonicalIntegerIndexString(key) && ObjectHasOwn(target, key)) {
+ const descriptor = ReflectGetOwnPropertyDescriptor(target, key);
+ descriptor.value = convertToNumber(descriptor.value);
+ return descriptor;
+ }
+ return ReflectGetOwnPropertyDescriptor(target, key);
+ },
+ defineProperty(target, key, descriptor) {
+ if (
+ isCanonicalIntegerIndexString(key) &&
+ ObjectHasOwn(target, key) &&
+ ObjectHasOwn(descriptor, "value"))
+ {
+ descriptor.value = roundToFloat16Bits(descriptor.value);
+ return ReflectDefineProperty(target, key, descriptor);
+ }
+ return ReflectDefineProperty(target, key, descriptor);
+ }
+});
+class Float16Array {
+ constructor(input, _byteOffset, _length) {
+ let float16bitsArray;
+ if (isFloat16Array(input)) {
+ float16bitsArray = ReflectConstruct(NativeUint16Array, [getFloat16BitsArray(input)], new.target);
+ } else if (isObject(input) && !isArrayBuffer(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(
+ length * BYTES_PER_ELEMENT
+ );
+ float16bitsArray = ReflectConstruct(NativeUint16Array, [data], new.target);
+ } else {
+ const iterator = input[SymbolIterator];
+ if (iterator != null && typeof iterator !== "function") {
+ throw NativeTypeError(ITERATOR_PROPERTY_IS_NOT_CALLABLE);
+ }
+ if (iterator != null) {
+ if (isOrdinaryArray(input)) {
+ list = input;
+ length = input.length;
+ } else {
+ list = [...input];
+ length = list.length;
+ }
+ } else {
+ list = input;
+ length = ToLength(list.length);
+ }
+ float16bitsArray = ReflectConstruct(NativeUint16Array, [length], new.target);
+ }
+ for (let i = 0; i < length; ++i) {
+ float16bitsArray[i] = roundToFloat16Bits(list[i]);
+ }
+ } else {
+ float16bitsArray = ReflectConstruct(NativeUint16Array, arguments, new.target);
+ }
+ const proxy = new NativeProxy(float16bitsArray, handler);
+ WeakMapPrototypeSet(float16bitsArrays, proxy, float16bitsArray);
+ return proxy;
+ }
+ static from(src, ...opts) {
+ const Constructor = this;
+ if (!ReflectHas(Constructor, brand)) {
+ throw NativeTypeError(
+ THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY
+ );
+ }
+ if (Constructor === Float16Array) {
+ if (isFloat16Array(src) && opts.length === 0) {
+ const float16bitsArray = getFloat16BitsArray(src);
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ return new Float16Array(
+ TypedArrayPrototypeGetBuffer(TypedArrayPrototypeSlice(uint16))
+ );
+ }
+ if (opts.length === 0) {
+ return new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ Uint16ArrayFrom(src, roundToFloat16Bits)
+ )
+ );
+ }
+ const mapFunc = opts[0];
+ const thisArg = opts[1];
+ return new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ Uint16ArrayFrom(src, function (val, ...args) {
+ return roundToFloat16Bits(
+ ReflectApply(mapFunc, this, [val, ...safeIfNeeded(args)])
+ );
+ }, thisArg)
+ )
+ );
+ }
+ let list;
+ let length;
+ const iterator = src[SymbolIterator];
+ if (iterator != null && typeof iterator !== "function") {
+ throw NativeTypeError(ITERATOR_PROPERTY_IS_NOT_CALLABLE);
+ }
+ if (iterator != null) {
+ if (isOrdinaryArray(src)) {
+ list = src;
+ length = src.length;
+ } else if (isOrdinaryNativeTypedArray(src)) {
+ list = src;
+ length = TypedArrayPrototypeGetLength(src);
+ } else {
+ list = [...src];
+ length = list.length;
+ }
+ } else {
+ if (src == null) {
+ throw NativeTypeError(
+ CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
+ );
+ }
+ list = NativeObject(src);
+ length = ToLength(list.length);
+ }
+ const array = new Constructor(length);
+ if (opts.length === 0) {
+ for (let i = 0; i < length; ++i) {
+ array[i] = list[i];
+ }
+ } else {
+ const mapFunc = opts[0];
+ const thisArg = opts[1];
+ for (let i = 0; i < length; ++i) {
+ array[i] = ReflectApply(mapFunc, thisArg, [list[i], i]);
+ }
+ }
+ return array;
+ }
+ static of(...items) {
+ const Constructor = this;
+ if (!ReflectHas(Constructor, brand)) {
+ throw NativeTypeError(
+ THIS_CONSTRUCTOR_IS_NOT_A_SUBCLASS_OF_FLOAT16ARRAY
+ );
+ }
+ const length = items.length;
+ if (Constructor === Float16Array) {
+ const proxy = new Float16Array(length);
+ const float16bitsArray = getFloat16BitsArray(proxy);
+ for (let i = 0; i < length; ++i) {
+ float16bitsArray[i] = roundToFloat16Bits(items[i]);
+ }
+ return proxy;
+ }
+ const array = new Constructor(length);
+ for (let i = 0; i < length; ++i) {
+ array[i] = items[i];
+ }
+ return array;
+ }
+ keys() {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ return TypedArrayPrototypeKeys(float16bitsArray);
+ }
+ values() {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ return wrap(function* () {
+ for (const val of TypedArrayPrototypeValues(float16bitsArray)) {
+ yield convertToNumber(val);
+ }
+ }());
+ }
+ entries() {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ return wrap(function* () {
+ for (const [i, val] of TypedArrayPrototypeEntries(float16bitsArray)) {
+ yield [i, convertToNumber(val)];
+ }
+ }());
+ }
+ at(index) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const relativeIndex = ToIntegerOrInfinity(index);
+ const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
+ if (k < 0 || k >= length) {
+ return;
+ }
+ return convertToNumber(float16bitsArray[k]);
+ }
+ map(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
+ if (Constructor === Float16Array) {
+ const proxy = new Float16Array(length);
+ const array = getFloat16BitsArray(proxy);
+ for (let i = 0; i < length; ++i) {
+ const val = convertToNumber(float16bitsArray[i]);
+ array[i] = roundToFloat16Bits(
+ ReflectApply(callback, thisArg, [val, i, this])
+ );
+ }
+ return proxy;
+ }
+ const array = new Constructor(length);
+ assertSpeciesTypedArray(array, length);
+ for (let i = 0; i < length; ++i) {
+ const val = convertToNumber(float16bitsArray[i]);
+ array[i] = ReflectApply(callback, thisArg, [val, i, this]);
+ }
+ return array;
+ }
+ filter(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ const kept = [];
+ for (let i = 0; i < length; ++i) {
+ const val = convertToNumber(float16bitsArray[i]);
+ if (ReflectApply(callback, thisArg, [val, i, this])) {
+ ArrayPrototypePush(kept, val);
+ }
+ }
+ const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
+ const array = new Constructor(kept);
+ assertSpeciesTypedArray(array);
+ return array;
+ }
+ reduce(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ if (length === 0 && opts.length === 0) {
+ throw NativeTypeError(REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE);
+ }
+ let accumulator, start;
+ if (opts.length === 0) {
+ accumulator = convertToNumber(float16bitsArray[0]);
+ start = 1;
+ } else {
+ accumulator = opts[0];
+ start = 0;
+ }
+ for (let i = start; i < length; ++i) {
+ accumulator = callback(
+ accumulator,
+ convertToNumber(float16bitsArray[i]),
+ i,
+ this
+ );
+ }
+ return accumulator;
+ }
+ reduceRight(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ if (length === 0 && opts.length === 0) {
+ throw NativeTypeError(REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE);
+ }
+ let accumulator, start;
+ if (opts.length === 0) {
+ accumulator = convertToNumber(float16bitsArray[length - 1]);
+ start = length - 2;
+ } else {
+ accumulator = opts[0];
+ start = length - 1;
+ }
+ for (let i = start; i >= 0; --i) {
+ accumulator = callback(
+ accumulator,
+ convertToNumber(float16bitsArray[i]),
+ i,
+ this
+ );
+ }
+ return accumulator;
+ }
+ forEach(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = 0; i < length; ++i) {
+ ReflectApply(callback, thisArg, [
+ convertToNumber(float16bitsArray[i]),
+ i,
+ this]
+ );
+ }
+ }
+ find(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = 0; i < length; ++i) {
+ const value = convertToNumber(float16bitsArray[i]);
+ if (ReflectApply(callback, thisArg, [value, i, this])) {
+ return value;
+ }
+ }
+ }
+ findIndex(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = 0; i < length; ++i) {
+ const value = convertToNumber(float16bitsArray[i]);
+ if (ReflectApply(callback, thisArg, [value, i, this])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ findLast(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = length - 1; i >= 0; --i) {
+ const value = convertToNumber(float16bitsArray[i]);
+ if (ReflectApply(callback, thisArg, [value, i, this])) {
+ return value;
+ }
+ }
+ }
+ findLastIndex(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = length - 1; i >= 0; --i) {
+ const value = convertToNumber(float16bitsArray[i]);
+ if (ReflectApply(callback, thisArg, [value, i, this])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ every(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = 0; i < length; ++i) {
+ if (
+ !ReflectApply(callback, thisArg, [
+ convertToNumber(float16bitsArray[i]),
+ i,
+ this]
+ ))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ some(callback, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const thisArg = opts[0];
+ for (let i = 0; i < length; ++i) {
+ if (
+ ReflectApply(callback, thisArg, [
+ convertToNumber(float16bitsArray[i]),
+ i,
+ this]
+ ))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ set(input, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const targetOffset = ToIntegerOrInfinity(opts[0]);
+ if (targetOffset < 0) {
+ throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
+ }
+ if (input == null) {
+ throw NativeTypeError(
+ CANNOT_CONVERT_UNDEFINED_OR_NULL_TO_OBJECT
+ );
+ }
+ if (isNativeBigIntTypedArray(input)) {
+ throw NativeTypeError(
+ CANNOT_MIX_BIGINT_AND_OTHER_TYPES
+ );
+ }
+ if (isFloat16Array(input)) {
+ return TypedArrayPrototypeSet(
+ getFloat16BitsArray(this),
+ getFloat16BitsArray(input),
+ targetOffset
+ );
+ }
+ if (isNativeTypedArray(input)) {
+ const buffer = TypedArrayPrototypeGetBuffer(input);
+ if (IsDetachedBuffer(buffer)) {
+ throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
+ }
+ }
+ const targetLength = TypedArrayPrototypeGetLength(float16bitsArray);
+ const src = NativeObject(input);
+ const srcLength = ToLength(src.length);
+ if (targetOffset === Infinity || srcLength + targetOffset > targetLength) {
+ throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
+ }
+ for (let i = 0; i < srcLength; ++i) {
+ float16bitsArray[i + targetOffset] = roundToFloat16Bits(src[i]);
+ }
+ }
+ reverse() {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ TypedArrayPrototypeReverse(float16bitsArray);
+ return this;
+ }
+ fill(value, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ TypedArrayPrototypeFill(
+ float16bitsArray,
+ roundToFloat16Bits(value),
+ ...safeIfNeeded(opts)
+ );
+ return this;
+ }
+ copyWithin(target, start, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ TypedArrayPrototypeCopyWithin(float16bitsArray, target, start, ...safeIfNeeded(opts));
+ return this;
+ }
+ sort(compareFn) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
+ TypedArrayPrototypeSort(float16bitsArray, (x, y) => {
+ return sortCompare(convertToNumber(x), convertToNumber(y));
+ });
+ return this;
+ }
+ slice(start, end) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
+ if (Constructor === Float16Array) {
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ return new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16, start, end)
+ )
+ );
+ }
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const relativeStart = ToIntegerOrInfinity(start);
+ const relativeEnd = end === undefined ? length : ToIntegerOrInfinity(end);
+ let k;
+ if (relativeStart === -Infinity) {
+ k = 0;
+ } else if (relativeStart < 0) {
+ k = length + relativeStart > 0 ? length + relativeStart : 0;
+ } else {
+ k = length < relativeStart ? length : relativeStart;
+ }
+ let final;
+ if (relativeEnd === -Infinity) {
+ final = 0;
+ } else if (relativeEnd < 0) {
+ final = length + relativeEnd > 0 ? length + relativeEnd : 0;
+ } else {
+ final = length < relativeEnd ? length : relativeEnd;
+ }
+ const count = final - k > 0 ? final - k : 0;
+ const array = new Constructor(count);
+ assertSpeciesTypedArray(array, count);
+ if (count === 0) {
+ return array;
+ }
+ const buffer = TypedArrayPrototypeGetBuffer(float16bitsArray);
+ if (IsDetachedBuffer(buffer)) {
+ throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
+ }
+ let n = 0;
+ while (k < final) {
+ array[n] = convertToNumber(float16bitsArray[k]);
+ ++k;
+ ++n;
+ }
+ return array;
+ }
+ subarray(begin, end) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const Constructor = SpeciesConstructor(float16bitsArray, Float16Array);
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const uint16Subarray = TypedArrayPrototypeSubarray(uint16, begin, end);
+ const array = new Constructor(
+ TypedArrayPrototypeGetBuffer(uint16Subarray),
+ TypedArrayPrototypeGetByteOffset(uint16Subarray),
+ TypedArrayPrototypeGetLength(uint16Subarray)
+ );
+ assertSpeciesTypedArray(array);
+ return array;
+ }
+ indexOf(element, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ let from = ToIntegerOrInfinity(opts[0]);
+ if (from === Infinity) {
+ return -1;
+ }
+ if (from < 0) {
+ from += length;
+ if (from < 0) {
+ from = 0;
+ }
+ }
+ for (let i = from; i < length; ++i) {
+ if (
+ ObjectHasOwn(float16bitsArray, i) &&
+ convertToNumber(float16bitsArray[i]) === element)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+ lastIndexOf(element, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ let from = opts.length >= 1 ? ToIntegerOrInfinity(opts[0]) : length - 1;
+ if (from === -Infinity) {
+ return -1;
+ }
+ if (from >= 0) {
+ from = from < length - 1 ? from : length - 1;
+ } else {
+ from += length;
+ }
+ for (let i = from; i >= 0; --i) {
+ if (
+ ObjectHasOwn(float16bitsArray, i) &&
+ convertToNumber(float16bitsArray[i]) === element)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+ includes(element, ...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ let from = ToIntegerOrInfinity(opts[0]);
+ if (from === Infinity) {
+ return false;
+ }
+ if (from < 0) {
+ from += length;
+ if (from < 0) {
+ from = 0;
+ }
+ }
+ const isNaN = NumberIsNaN(element);
+ for (let i = from; i < length; ++i) {
+ const value = convertToNumber(float16bitsArray[i]);
+ if (isNaN && NumberIsNaN(value)) {
+ return true;
+ }
+ if (value === element) {
+ return true;
+ }
+ }
+ return false;
+ }
+ join(separator) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const array = copyToArray(float16bitsArray);
+ return ArrayPrototypeJoin(array, separator);
+ }
+ toLocaleString(...opts) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const array = copyToArray(float16bitsArray);
+ return ArrayPrototypeToLocaleString(array, ...safeIfNeeded(opts));
+ }
+ get [SymbolToStringTag]() {
+ if (isFloat16Array(this)) {
+ return "Float16Array";
+ }
+ }
+}
+ObjectDefineProperty(Float16Array, "BYTES_PER_ELEMENT", {
+ value: BYTES_PER_ELEMENT
+});
+ObjectDefineProperty(Float16Array, brand, {});
+ReflectSetPrototypeOf(Float16Array, TypedArray);
+const Float16ArrayPrototype = Float16Array.prototype;
+ObjectDefineProperty(Float16ArrayPrototype, "BYTES_PER_ELEMENT", {
+ value: BYTES_PER_ELEMENT
+});
+ObjectDefineProperty(Float16ArrayPrototype, SymbolIterator, {
+ value: Float16ArrayPrototype.values,
+ writable: true,
+ configurable: true
+});
+ReflectSetPrototypeOf(Float16ArrayPrototype, TypedArrayPrototype);
+
+function isTypedArray(target) {
+ return isNativeTypedArray(target) || isFloat16Array(target);
+}
+
+function getFloat16(dataView, byteOffset, ...opts) {
+ return convertToNumber(
+ DataViewPrototypeGetUint16(dataView, byteOffset, ...safeIfNeeded(opts))
+ );
+}
+function setFloat16(dataView, byteOffset, value, ...opts) {
+ return DataViewPrototypeSetUint16(
+ dataView,
+ byteOffset,
+ roundToFloat16Bits(value),
+ ...safeIfNeeded(opts)
+ );
+}
+
+function hfround(x) {
+ const number = +x;
+ if (!NumberIsFinite(number) || number === 0) {
+ return number;
+ }
+ const x16 = roundToFloat16Bits(number);
+ return convertToNumber(x16);
+}
+
+export { Float16Array, getFloat16, hfround, isFloat16Array, isTypedArray, setFloat16 }; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/README.md b/testing/web-platform/mozilla/tests/webgpu/resources/README.md
new file mode 100644
index 0000000000..824f82b998
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/README.md
@@ -0,0 +1,15 @@
+Always use `getResourcePath()` to get the appropriate path to these resources depending
+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
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-180.mp4 b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-180.mp4
new file mode 100644
index 0000000000..1f0e9094a5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-180.mp4
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-270.mp4 b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-270.mp4
new file mode 100644
index 0000000000..e0480ceff2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-270.mp4
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-90.mp4 b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-90.mp4
new file mode 100644
index 0000000000..9a6261056e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601-rotate-90.mp4
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601.mp4 b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601.mp4
new file mode 100644
index 0000000000..81a5ade435
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-h264-bt601.mp4
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-theora-bt601.ogv b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-theora-bt601.ogv
new file mode 100644
index 0000000000..79ed41163c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-theora-bt601.ogv
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp8-bt601.webm b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp8-bt601.webm
new file mode 100644
index 0000000000..20a2178596
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp8-bt601.webm
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt601.webm b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt601.webm
new file mode 100644
index 0000000000..a4044a9209
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt601.webm
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt709.webm b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt709.webm
new file mode 100644
index 0000000000..189e422035
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors-vp9-bt709.webm
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/four-colors.png b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors.png
new file mode 100644
index 0000000000..c26c3d4865
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/four-colors.png
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/resources/webgpu.png b/testing/web-platform/mozilla/tests/webgpu/resources/webgpu.png
new file mode 100644
index 0000000000..eec0d6eb90
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/resources/webgpu.png
Binary files differ
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapter.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapter.spec.js
new file mode 100644
index 0000000000..ca97535007
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapter.spec.js
@@ -0,0 +1,124 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for GPU.requestAdapter.
+
+Test all possible options to requestAdapter.
+default, low-power, and high performance should all always return adapters.
+forceFallbackAdapter may or may not return an adapter.
+
+GPU.requestAdapter can technically return null for any reason
+but we need test functionality so the test requires an adapter except
+when forceFallbackAdapter is true.
+
+The test runs simple compute shader is run that fills a buffer with consecutive
+values and then checks the result to test the adapter for basic functionality.
+`;import { Fixture } from '../../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { getGPU } from '../../../../common/util/navigator_gpu.js';
+import { assert, objectEquals, iterRange } from '../../../../common/util/util.js';
+
+export const g = makeTestGroup(Fixture);
+
+const powerPreferenceModes = [
+undefined,
+'low-power',
+'high-performance'];
+
+const forceFallbackOptions = [undefined, false, true];
+
+async function testAdapter(adapter) {
+ assert(adapter !== null, 'Failed to get adapter.');
+ const device = await adapter.requestDevice();
+
+ assert(device !== null, 'Failed to get device.');
+
+ const kOffset = 1230000;
+ const pipeline = device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: device.createShaderModule({
+ code: `
+ struct Buffer { data: array<u32>, };
+
+ @group(0) @binding(0) var<storage, read_write> buffer: Buffer;
+ @compute @workgroup_size(1u) fn main(
+ @builtin(global_invocation_id) id: vec3<u32>) {
+ buffer.data[id.x] = id.x + ${kOffset}u;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const kNumElements = 64;
+ const kBufferSize = kNumElements * 4;
+ const buffer = device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+
+ const resultBuffer = device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer } }]
+ });
+
+ const encoder = device.createCommandEncoder();
+
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(kNumElements);
+ pass.end();
+
+ encoder.copyBufferToBuffer(buffer, 0, resultBuffer, 0, kBufferSize);
+
+ device.queue.submit([encoder.finish()]);
+
+ const expected = new Uint32Array([...iterRange(kNumElements, (x) => x + kOffset)]);
+
+ await resultBuffer.mapAsync(GPUMapMode.READ);
+ const actual = new Uint32Array(resultBuffer.getMappedRange());
+
+ assert(objectEquals(actual, expected), 'compute pipeline ran');
+
+ resultBuffer.destroy();
+ buffer.destroy();
+ device.destroy();
+}
+
+g.test('requestAdapter').
+desc(`request adapter with all possible options and check for basic functionality`).
+params((u) =>
+u.
+combine('powerPreference', powerPreferenceModes).
+combine('forceFallbackAdapter', forceFallbackOptions)
+).
+fn(async (t) => {
+ const { powerPreference, forceFallbackAdapter } = t.params;
+ const adapter = await getGPU(t.rec).requestAdapter({
+ ...(powerPreference !== undefined && { powerPreference }),
+ ...(forceFallbackAdapter !== undefined && { forceFallbackAdapter })
+ });
+
+ // failing to create an adapter when forceFallbackAdapter is true is ok.
+ if (forceFallbackAdapter && !adapter) {
+ t.skip('No adapter available');
+ return;
+ }
+
+ await testAdapter(adapter);
+});
+
+g.test('requestAdapter_no_parameters').
+desc(`request adapter with no parameters`).
+fn(async (t) => {
+ const adapter = await getGPU(t.rec).requestAdapter();
+ await testAdapter(adapter);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapterInfo.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapterInfo.spec.js
new file mode 100644
index 0000000000..170f5c5385
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestAdapterInfo.spec.js
@@ -0,0 +1,54 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests various ways of calling GPUAdapter.requestAdapterInfo.
+
+TODO:
+- Find a way to perform tests with and without user activation
+`;import { Fixture } from '../../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { getGPU } from '../../../../common/util/navigator_gpu.js';
+import { assert } from '../../../../common/util/util.js';
+
+export const g = makeTestGroup(Fixture);
+
+const normalizedIdentifierRegex = /^$|^[a-z0-9]+(-[a-z0-9]+)*$/;
+
+g.test('adapter_info').
+desc(
+ `
+ Test that calling requestAdapterInfo with no arguments:
+ - Returns a GPUAdapterInfo structure
+ - Every member in the structure except description is properly formatted`
+).
+fn(async (t) => {
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const adapterInfo = await adapter.requestAdapterInfo();
+
+ t.expect(
+ normalizedIdentifierRegex.test(adapterInfo.vendor),
+ `adapterInfo.vendor should be a normalized identifier. But it's '${adapterInfo.vendor}'`
+ );
+
+ t.expect(
+ normalizedIdentifierRegex.test(adapterInfo.architecture),
+ `adapterInfo.architecture should be a normalized identifier. But it's '${adapterInfo.architecture}'`
+ );
+
+ t.expect(
+ normalizedIdentifierRegex.test(adapterInfo.device),
+ `adapterInfo.device should be a normalized identifier. But it's '${adapterInfo.device}'`
+ );
+});
+
+g.test('adapter_info_with_hints').
+desc(
+ `
+ Test that calling requestAdapterInfo with hints:
+ - Rejects without user activation
+ - Succeed with user activation`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestDevice.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestDevice.spec.js
new file mode 100644
index 0000000000..e01474f3f7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/adapter/requestDevice.spec.js
@@ -0,0 +1,376 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test GPUAdapter.requestDevice.
+
+Note tests explicitly destroy created devices so that tests don't have to wait for GC to clean up
+potentially limited native resources.
+`;import { Fixture } from '../../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { getGPU } from '../../../../common/util/navigator_gpu.js';
+import { assert, assertReject, raceWithRejectOnTimeout } from '../../../../common/util/util.js';
+import {
+ getDefaultLimitsForAdapter,
+ kFeatureNames,
+ kLimits,
+ kLimitClasses } from
+'../../../capability_info.js';
+import { clamp, isPowerOfTwo } from '../../../util/math.js';
+
+export const g = makeTestGroup(Fixture);
+
+g.test('default').
+desc(
+ `
+ Test requesting the device with a variation of default parameters.
+ - No features listed in default device
+ - Default limits`
+).
+paramsSubcasesOnly((u) =>
+u.combine('args', [
+[],
+[undefined],
+[{}],
+[{ requiredFeatures: [], requiredLimits: {} }]]
+)
+).
+fn(async (t) => {
+ const { args } = t.params;
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+ const device = await adapter.requestDevice(...args);
+ assert(device !== null);
+
+ // Default device should have no features.
+ t.expect(device.features.size === 0, 'Default device should not have any features');
+ // All limits should be defaults.
+ const limitInfo = getDefaultLimitsForAdapter(adapter);
+ for (const limit of kLimits) {
+ t.expect(
+ device.limits[limit] === limitInfo[limit].default,
+ `Expected ${limit} == default: ${device.limits[limit]} != ${limitInfo[limit].default}`
+ );
+ }
+
+ device.destroy();
+});
+
+g.test('invalid').
+desc(
+ `
+ Test that requesting device on an invalid adapter resolves with lost device.
+ - Induce invalid adapter via a device lost from a device.destroy()
+ - Check the device is lost with reason 'destroyed'
+ - Try creating another device on the now-stale adapter
+ - Check that returns a device lost with 'unknown'
+ `
+).
+fn(async (t) => {
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ {
+ // Request a device and destroy it immediately afterwards.
+ const device = await adapter.requestDevice();
+ assert(device !== null);
+ device.destroy();
+ const lostInfo = await device.lost;
+ t.expect(lostInfo.reason === 'destroyed');
+ }
+
+ // The adapter should now be invalid since a device was lost. Requesting another device should
+ // return an already lost device.
+ const kTimeoutMS = 1000;
+ const device = await adapter.requestDevice();
+ const lost = await raceWithRejectOnTimeout(device.lost, kTimeoutMS, 'device was not lost');
+ t.expect(lost.reason === 'unknown');
+});
+
+g.test('stale').
+desc(
+ `
+ Test that adapter.requestDevice() can successfully return a device once, and once only.
+ - Tests that we can successfully resolve after serial and concurrent rejections.
+ - Tests that consecutive valid attempts only succeeds the first time, returning lost device otherwise.`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('initialError', [undefined, 'TypeError', 'OperationError']).
+combine('awaitInitialError', [true, false]).
+combine('awaitSuccess', [true, false]).
+unless(
+ ({ initialError, awaitInitialError }) => initialError === undefined && awaitInitialError
+)
+).
+fn(async (t) => {
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const { initialError, awaitInitialError, awaitSuccess } = t.params;
+
+ switch (initialError) {
+ case undefined:
+ break;
+ case 'TypeError':
+ // Cause a type error by requesting with an unknown feature.
+ if (awaitInitialError) {
+ await assertReject(
+ 'TypeError',
+ adapter.requestDevice({ requiredFeatures: ['unknown-feature'] })
+ );
+ } else {
+ t.shouldReject(
+ 'TypeError',
+ adapter.requestDevice({ requiredFeatures: ['unknown-feature'] })
+ );
+ }
+ break;
+ case 'OperationError':
+ // Cause an operation error by requesting with an alignment limit that is not a power of 2.
+ if (awaitInitialError) {
+ await assertReject(
+ 'OperationError',
+ adapter.requestDevice({ requiredLimits: { minUniformBufferOffsetAlignment: 255 } })
+ );
+ } else {
+ t.shouldReject(
+ 'OperationError',
+ adapter.requestDevice({ requiredLimits: { minUniformBufferOffsetAlignment: 255 } })
+ );
+ }
+ break;
+ }
+
+ let device = undefined;
+ const promise = adapter.requestDevice();
+ if (awaitSuccess) {
+ device = await promise;
+ assert(device !== null);
+ } else {
+ t.shouldResolve(
+ (async () => {
+ const device = await promise;
+ device.destroy();
+ })()
+ );
+ }
+
+ const kTimeoutMS = 1000;
+ const lostDevice = await adapter.requestDevice();
+ const lost = await raceWithRejectOnTimeout(
+ lostDevice.lost,
+ kTimeoutMS,
+ 'adapter was not stale'
+ );
+ t.expect(lost.reason === 'unknown');
+
+ // Make sure to destroy the valid device after trying to get a second one. Otherwise, the second
+ // device may fail because the adapter is put into an invalid state from the destroy.
+ if (device) {
+ device.destroy();
+ }
+});
+
+g.test('features,unknown').
+desc(
+ `
+ Test requesting device with an unknown feature.`
+).
+fn(async (t) => {
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ t.shouldReject(
+ 'TypeError',
+ adapter.requestDevice({ requiredFeatures: ['unknown-feature'] })
+ );
+});
+
+g.test('features,known').
+desc(
+ `
+ Test requesting device with all features.
+ - Succeeds with device supporting feature if adapter supports the feature.
+ - Rejects if the adapter does not support the feature.`
+).
+params((u) => u.combine('feature', kFeatureNames)).
+fn(async (t) => {
+ const { feature } = t.params;
+
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const promise = adapter.requestDevice({ requiredFeatures: [feature] });
+ if (adapter.features.has(feature)) {
+ const device = await promise;
+ t.expect(device.features.has(feature), 'Device should include the required feature');
+ } else {
+ t.shouldReject('TypeError', promise);
+ }
+});
+
+g.test('limits,unknown').
+desc(
+ `
+ Test that specifying limits that aren't part of the supported limit set causes
+ requestDevice to reject.`
+).
+fn(async (t) => {
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const requiredLimits = { unknownLimitName: 9000 };
+
+ t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
+});
+
+g.test('limits,supported').
+desc(
+ `
+ Test that each supported limit can be specified with valid values.
+ - Tests each limit with the default values given by the spec
+ - Tests each limit with the supported values given by the adapter`
+).
+params((u) =>
+u.combine('limit', kLimits).beginSubcases().combine('limitValue', ['default', 'adapter'])
+).
+fn(async (t) => {
+ const { limit, limitValue } = t.params;
+
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const limitInfo = getDefaultLimitsForAdapter(adapter);
+ let value = -1;
+ switch (limitValue) {
+ case 'default':
+ value = limitInfo[limit].default;
+ break;
+ case 'adapter':
+ value = adapter.limits[limit];
+ break;
+ }
+
+ const device = await adapter.requestDevice({ requiredLimits: { [limit]: value } });
+ assert(device !== null);
+ t.expect(
+ device.limits[limit] === value,
+ 'Devices reported limit should match the required limit'
+ );
+ device.destroy();
+});
+
+g.test('limit,better_than_supported').
+desc(
+ `
+ Test that specifying a better limit than what the adapter supports causes requestDevice to
+ reject.
+ - Tests each limit
+ - Tests requesting better limits by various amounts`
+).
+params((u) =>
+u.
+combine('limit', kLimits).
+beginSubcases().
+expandWithParams((p) => {
+ switch (kLimitClasses[p.limit]) {
+ case 'maximum':
+ return [
+ { mul: 1, add: 1 },
+ { mul: 1, add: 100 }];
+
+ case 'alignment':
+ return [
+ { mul: 1, add: -1 },
+ { mul: 1 / 2, add: 0 },
+ { mul: 1 / 1024, add: 0 }];
+
+ }
+})
+).
+fn(async (t) => {
+ const { limit, mul, add } = t.params;
+
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const limitInfo = getDefaultLimitsForAdapter(adapter);
+ const value = adapter.limits[limit] * mul + add;
+ const requiredLimits = {
+ [limit]: clamp(value, { min: 0, max: limitInfo[limit].maximumValue })
+ };
+
+ t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
+});
+
+g.test('limit,worse_than_default').
+desc(
+ `
+ Test that specifying a worse limit than the default values required by the spec cause the value
+ to clamp.
+ - Tests each limit
+ - Tests requesting worse limits by various amounts`
+).
+params((u) =>
+u.
+combine('limit', kLimits).
+beginSubcases().
+expandWithParams((p) => {
+ switch (kLimitClasses[p.limit]) {
+ case 'maximum':
+ return [
+ { mul: 1, add: -1 },
+ { mul: 1, add: -100 }];
+
+ case 'alignment':
+ return [
+ { mul: 1, add: 1 },
+ { mul: 2, add: 0 },
+ { mul: 1024, add: 0 }];
+
+ }
+})
+).
+fn(async (t) => {
+ const { limit, mul, add } = t.params;
+
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+
+ const limitInfo = getDefaultLimitsForAdapter(adapter);
+ const value = limitInfo[limit].default * mul + add;
+ const requiredLimits = {
+ [limit]: clamp(value, { min: 0, max: limitInfo[limit].maximumValue })
+ };
+
+ let success;
+ switch (limitInfo[limit].class) {
+ case 'alignment':
+ success = isPowerOfTwo(value);
+ break;
+ case 'maximum':
+ success = true;
+ break;
+ }
+
+ if (success) {
+ const device = await adapter.requestDevice({ requiredLimits });
+ assert(device !== null);
+ t.expect(
+ device.limits[limit] === limitInfo[limit].default,
+ 'Devices reported limit should match the default limit'
+ );
+ device.destroy();
+ } else {
+ t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map.spec.js
new file mode 100644
index 0000000000..2d4408e6c0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map.spec.js
@@ -0,0 +1,510 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test the operation of buffer mapping, specifically the data contents written via
+map-write/mappedAtCreation, and the contents of buffers returned by getMappedRange on
+buffers which are mapped-read/mapped-write/mappedAtCreation.
+
+range: used for getMappedRange
+mapRegion: used for mapAsync
+
+mapRegionBoundModes is used to get mapRegion from range:
+ - default-expand: expand mapRegion to buffer bound by setting offset/size to undefined
+ - explicit-expand: expand mapRegion to buffer bound by explicitly calculating offset/size
+ - minimal: make mapRegion to be the same as range which is the minimal range to make getMappedRange input valid
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, memcpy } from '../../../../common/util/util.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
+
+import { MappingTest } from './mapping_test.js';
+
+export const g = makeTestGroup(MappingTest);
+
+const kSubcases = [
+{ size: 0, range: [] },
+{ size: 0, range: [undefined] },
+{ size: 0, range: [undefined, undefined] },
+{ size: 0, range: [0] },
+{ size: 0, range: [0, undefined] },
+{ size: 0, range: [0, 0] },
+{ size: 12, range: [] },
+{ size: 12, range: [undefined] },
+{ size: 12, range: [undefined, undefined] },
+{ size: 12, range: [0] },
+{ size: 12, range: [0, undefined] },
+{ size: 12, range: [0, 12] },
+{ size: 12, range: [0, 0] },
+{ size: 12, range: [8] },
+{ size: 12, range: [8, undefined] },
+{ size: 12, range: [8, 4] },
+{ size: 28, range: [8, 8] },
+{ size: 28, range: [8, 12] },
+{ size: 512 * 1024, range: [] }];
+
+
+function reifyMapRange(bufferSize, range) {
+ const offset = range[0] ?? 0;
+ return [offset, range[1] ?? bufferSize - offset];
+}
+
+const mapRegionBoundModes = ['default-expand', 'explicit-expand', 'minimal'];
+
+
+function getRegionForMap(
+bufferSize,
+range,
+{
+ mapAsyncRegionLeft,
+ mapAsyncRegionRight
+
+
+
+})
+{
+ const regionLeft = mapAsyncRegionLeft === 'minimal' ? range[0] : 0;
+ const regionRight = mapAsyncRegionRight === 'minimal' ? range[0] + range[1] : bufferSize;
+ return [
+ mapAsyncRegionLeft === 'default-expand' ? undefined : regionLeft,
+ mapAsyncRegionRight === 'default-expand' ? undefined : regionRight - regionLeft];
+
+}
+
+g.test('mapAsync,write').
+desc(
+ `Use map-write to write to various ranges of variously-sized buffers, then expectContents
+(which does copyBufferToBuffer + map-read) to ensure the contents were written.`
+).
+params((u) =>
+u.
+combine('mapAsyncRegionLeft', mapRegionBoundModes).
+combine('mapAsyncRegionRight', mapRegionBoundModes).
+beginSubcases().
+combineWithParams(kSubcases)
+).
+fn(async (t) => {
+ const { size, range } = t.params;
+ const [rangeOffset, rangeSize] = reifyMapRange(size, range);
+
+ const buffer = t.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
+ });
+
+ const mapRegion = getRegionForMap(size, [rangeOffset, rangeSize], t.params);
+ await buffer.mapAsync(GPUMapMode.WRITE, ...mapRegion);
+ const arrayBuffer = buffer.getMappedRange(...range);
+ t.checkMapWrite(buffer, rangeOffset, arrayBuffer, rangeSize);
+});
+
+g.test('mapAsync,write,unchanged_ranges_preserved').
+desc(
+ `Use mappedAtCreation or mapAsync to write to various ranges of variously-sized buffers, then
+use mapAsync to map a different range and zero it out. Finally use expectGPUBufferValuesEqual
+(which does copyBufferToBuffer + map-read) to verify that contents originally written outside the
+second mapped range were not altered.`
+).
+params((u) =>
+u.
+beginSubcases().
+combine('mappedAtCreation', [false, true]).
+combineWithParams([
+{ size: 12, range1: [], range2: [8] },
+{ size: 12, range1: [], range2: [0, 8] },
+{ size: 12, range1: [0, 8], range2: [8] },
+{ size: 12, range1: [8], range2: [0, 8] },
+{ size: 28, range1: [], range2: [8, 8] },
+{ size: 28, range1: [8, 16], range2: [16, 8] },
+{ size: 32, range1: [16, 12], range2: [8, 16] },
+{ size: 32, range1: [8, 8], range2: [24, 4] }]
+)
+).
+fn(async (t) => {
+ const { size, range1, range2, mappedAtCreation } = t.params;
+ const [rangeOffset1, rangeSize1] = reifyMapRange(size, range1);
+ const [rangeOffset2, rangeSize2] = reifyMapRange(size, range2);
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation,
+ size,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
+ });
+
+ // If the buffer is not mappedAtCreation map it now.
+ if (!mappedAtCreation) {
+ await buffer.mapAsync(GPUMapMode.WRITE);
+ }
+
+ // Set the initial contents of the buffer.
+ const init = buffer.getMappedRange(...range1);
+
+ assert(init.byteLength === rangeSize1);
+ const expectedBuffer = new ArrayBuffer(size);
+ const expected = new Uint32Array(
+ expectedBuffer,
+ rangeOffset1,
+ rangeSize1 / Uint32Array.BYTES_PER_ELEMENT
+ );
+ const data = new Uint32Array(init);
+ for (let i = 0; i < data.length; ++i) {
+ data[i] = expected[i] = i + 1;
+ }
+ buffer.unmap();
+
+ // Write to a second range of the buffer
+ await buffer.mapAsync(GPUMapMode.WRITE, ...range2);
+ const init2 = buffer.getMappedRange(...range2);
+
+ assert(init2.byteLength === rangeSize2);
+ const expected2 = new Uint32Array(
+ expectedBuffer,
+ rangeOffset2,
+ rangeSize2 / Uint32Array.BYTES_PER_ELEMENT
+ );
+ const data2 = new Uint32Array(init2);
+ for (let i = 0; i < data2.length; ++i) {
+ data2[i] = expected2[i] = 0;
+ }
+ buffer.unmap();
+
+ // Verify that the range of the buffer which was not overwritten was preserved.
+ t.expectGPUBufferValuesEqual(buffer, expected, rangeOffset1);
+});
+
+g.test('mapAsync,read').
+desc(
+ `Use mappedAtCreation to initialize various ranges of variously-sized buffers, then
+map-read and check the read-back result.`
+).
+params((u) =>
+u.
+combine('mapAsyncRegionLeft', mapRegionBoundModes).
+combine('mapAsyncRegionRight', mapRegionBoundModes).
+beginSubcases().
+combineWithParams(kSubcases)
+).
+fn(async (t) => {
+ const { size, range } = t.params;
+ const [rangeOffset, rangeSize] = reifyMapRange(size, range);
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
+ });
+ const init = buffer.getMappedRange(...range);
+
+ assert(init.byteLength === rangeSize);
+ const expected = new Uint32Array(new ArrayBuffer(rangeSize));
+ const data = new Uint32Array(init);
+ for (let i = 0; i < data.length; ++i) {
+ data[i] = expected[i] = i + 1;
+ }
+ buffer.unmap();
+
+ const mapRegion = getRegionForMap(size, [rangeOffset, rangeSize], t.params);
+ await buffer.mapAsync(GPUMapMode.READ, ...mapRegion);
+ const actual = new Uint8Array(buffer.getMappedRange(...range));
+ t.expectOK(checkElementsEqual(actual, new Uint8Array(expected.buffer)));
+});
+
+g.test('mapAsync,read,typedArrayAccess').
+desc(`Use various TypedArray types to read back from a mapped buffer`).
+params((u) =>
+u.
+combine('mapAsyncRegionLeft', mapRegionBoundModes).
+combine('mapAsyncRegionRight', mapRegionBoundModes).
+beginSubcases().
+combineWithParams([
+{ size: 80, range: [] },
+{ size: 160, range: [] },
+{ size: 160, range: [0, 80] },
+{ size: 160, range: [80] },
+{ size: 160, range: [40, 120] },
+{ size: 160, range: [40] }]
+)
+).
+fn(async (t) => {
+ const { size, range } = t.params;
+ const [rangeOffset, rangeSize] = reifyMapRange(size, range);
+
+ // Fill an array buffer with a variety of values of different types.
+ const expectedArrayBuffer = new ArrayBuffer(80);
+ const uint8Expected = new Uint8Array(expectedArrayBuffer, 0, 2);
+ uint8Expected[0] = 1;
+ uint8Expected[1] = 255;
+
+ const int8Expected = new Int8Array(expectedArrayBuffer, 2, 2);
+ int8Expected[0] = -1;
+ int8Expected[1] = 127;
+
+ const uint16Expected = new Uint16Array(expectedArrayBuffer, 4, 2);
+ uint16Expected[0] = 1;
+ uint16Expected[1] = 65535;
+
+ const int16Expected = new Int16Array(expectedArrayBuffer, 8, 2);
+ int16Expected[0] = -1;
+ int16Expected[1] = 32767;
+
+ const uint32Expected = new Uint32Array(expectedArrayBuffer, 12, 2);
+ uint32Expected[0] = 1;
+ uint32Expected[1] = 4294967295;
+
+ const int32Expected = new Int32Array(expectedArrayBuffer, 20, 2);
+ int32Expected[2] = -1;
+ int32Expected[3] = 2147483647;
+
+ const float32Expected = new Float32Array(expectedArrayBuffer, 28, 3);
+ float32Expected[0] = 1;
+ float32Expected[1] = -1;
+ float32Expected[2] = 12345.6789;
+
+ const float64Expected = new Float64Array(expectedArrayBuffer, 40, 5);
+ float64Expected[0] = 1;
+ float64Expected[1] = -1;
+ float64Expected[2] = 12345.6789;
+ float64Expected[3] = Number.MAX_VALUE;
+ float64Expected[4] = Number.MIN_VALUE;
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
+ });
+ const init = buffer.getMappedRange(...range);
+
+ // Copy the expected values into the mapped range.
+ assert(init.byteLength === rangeSize);
+ memcpy({ src: expectedArrayBuffer }, { dst: init });
+ buffer.unmap();
+
+ const mapRegion = getRegionForMap(size, [rangeOffset, rangeSize], t.params);
+ await buffer.mapAsync(GPUMapMode.READ, ...mapRegion);
+ const mappedArrayBuffer = buffer.getMappedRange(...range);
+ t.expectOK(checkElementsEqual(new Uint8Array(mappedArrayBuffer, 0, 2), uint8Expected));
+ t.expectOK(checkElementsEqual(new Int8Array(mappedArrayBuffer, 2, 2), int8Expected));
+ t.expectOK(checkElementsEqual(new Uint16Array(mappedArrayBuffer, 4, 2), uint16Expected));
+ t.expectOK(checkElementsEqual(new Int16Array(mappedArrayBuffer, 8, 2), int16Expected));
+ t.expectOK(checkElementsEqual(new Uint32Array(mappedArrayBuffer, 12, 2), uint32Expected));
+ t.expectOK(checkElementsEqual(new Int32Array(mappedArrayBuffer, 20, 2), int32Expected));
+ t.expectOK(checkElementsEqual(new Float32Array(mappedArrayBuffer, 28, 3), float32Expected));
+ t.expectOK(checkElementsEqual(new Float64Array(mappedArrayBuffer, 40, 5), float64Expected));
+});
+
+g.test('mappedAtCreation').
+desc(
+ `Use mappedAtCreation to write to various ranges of variously-sized buffers created either
+with or without the MAP_WRITE usage (since this could affect the mappedAtCreation upload path),
+then expectContents (which does copyBufferToBuffer + map-read) to ensure the contents were written.`
+).
+params((u) =>
+u //
+.combine('mappable', [false, true]).
+beginSubcases().
+combineWithParams(kSubcases)
+).
+fn((t) => {
+ const { size, range, mappable } = t.params;
+ const [, rangeSize] = reifyMapRange(size, range);
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size,
+ usage: GPUBufferUsage.COPY_SRC | (mappable ? GPUBufferUsage.MAP_WRITE : 0)
+ });
+ const arrayBuffer = buffer.getMappedRange(...range);
+ t.checkMapWrite(buffer, range[0] ?? 0, arrayBuffer, rangeSize);
+});
+
+g.test('remapped_for_write').
+desc(
+ `Use mappedAtCreation or mapAsync to write to various ranges of variously-sized buffers created
+with the MAP_WRITE usage, then mapAsync again and ensure that the previously written values are
+still present in the mapped buffer.`
+).
+params((u) =>
+u //
+.combine('mapAsyncRegionLeft', mapRegionBoundModes).
+combine('mapAsyncRegionRight', mapRegionBoundModes).
+beginSubcases().
+combine('mappedAtCreation', [false, true]).
+combineWithParams(kSubcases)
+).
+fn(async (t) => {
+ const { size, range, mappedAtCreation } = t.params;
+ const [rangeOffset, rangeSize] = reifyMapRange(size, range);
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation,
+ size,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
+ });
+
+ // If the buffer is not mappedAtCreation map it now.
+ if (!mappedAtCreation) {
+ await buffer.mapAsync(GPUMapMode.WRITE);
+ }
+
+ // Set the initial contents of the buffer.
+ const init = buffer.getMappedRange(...range);
+
+ assert(init.byteLength === rangeSize);
+ const expected = new Uint32Array(new ArrayBuffer(rangeSize));
+ const data = new Uint32Array(init);
+ for (let i = 0; i < data.length; ++i) {
+ data[i] = expected[i] = i + 1;
+ }
+ buffer.unmap();
+
+ // Check that upon remapping the for WRITE the values in the buffer are
+ // still the same.
+ const mapRegion = getRegionForMap(size, [rangeOffset, rangeSize], t.params);
+ await buffer.mapAsync(GPUMapMode.WRITE, ...mapRegion);
+ const actual = new Uint8Array(buffer.getMappedRange(...range));
+ t.expectOK(checkElementsEqual(actual, new Uint8Array(expected.buffer)));
+});
+
+g.test('mappedAtCreation,mapState').
+desc('Test that exposed map state of buffer created with mappedAtCreation has expected values.').
+params((u) =>
+u.
+combine('usageType', ['invalid', 'read', 'write']).
+combine('afterUnmap', [false, true]).
+combine('afterDestroy', [false, true])
+).
+fn((t) => {
+ const { usageType, afterUnmap, afterDestroy } = t.params;
+ const usage =
+ usageType === 'read' ?
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ :
+ usageType === 'write' ?
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE :
+ 0;
+ const validationError = usage === 0;
+ const size = 8;
+ const range = [0, 8];
+
+ let buffer;
+ t.expectValidationError(() => {
+ buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size,
+ usage
+ });
+ }, validationError);
+
+ // mapState must be "mapped" regardless of validation error
+ t.expect(buffer.mapState === 'mapped');
+
+ // getMappedRange must not change the map state
+ buffer.getMappedRange(...range);
+ t.expect(buffer.mapState === 'mapped');
+
+ if (afterUnmap) {
+ buffer.unmap();
+ t.expect(buffer.mapState === 'unmapped');
+ }
+
+ if (afterDestroy) {
+ buffer.destroy();
+ t.expect(buffer.mapState === 'unmapped');
+ }
+});
+
+g.test('mapAsync,mapState').
+desc('Test that exposed map state of buffer mapped with mapAsync has expected values.').
+params((u) =>
+u.
+combine('usageType', ['invalid', 'read', 'write']).
+combine('mapModeType', ['READ', 'WRITE']).
+combine('beforeUnmap', [false, true]).
+combine('beforeDestroy', [false, true]).
+combine('afterUnmap', [false, true]).
+combine('afterDestroy', [false, true])
+).
+fn(async (t) => {
+ const { usageType, mapModeType, beforeUnmap, beforeDestroy, afterUnmap, afterDestroy } =
+ t.params;
+ const size = 8;
+ const range = [0, 8];
+ const usage =
+ usageType === 'read' ?
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ :
+ usageType === 'write' ?
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE :
+ 0;
+ const bufferCreationValidationError = usage === 0;
+ const mapMode = GPUMapMode[mapModeType];
+
+ let buffer;
+ t.expectValidationError(() => {
+ buffer = t.device.createBuffer({
+ mappedAtCreation: false,
+ size,
+ usage
+ });
+ }, bufferCreationValidationError);
+
+ t.expect(buffer.mapState === 'unmapped');
+
+ {
+ const mapAsyncValidationError =
+ bufferCreationValidationError ||
+ mapMode === GPUMapMode.READ && !(usage & GPUBufferUsage.MAP_READ) ||
+ mapMode === GPUMapMode.WRITE && !(usage & GPUBufferUsage.MAP_WRITE);
+ let promise;
+ t.expectValidationError(() => {
+ promise = buffer.mapAsync(mapMode);
+ }, mapAsyncValidationError);
+ t.expect(buffer.mapState === 'pending');
+
+ try {
+ if (beforeUnmap) {
+ buffer.unmap();
+ t.expect(buffer.mapState === 'unmapped');
+ }
+ if (beforeDestroy) {
+ buffer.destroy();
+ t.expect(buffer.mapState === 'unmapped');
+ }
+
+ await promise;
+ t.expect(buffer.mapState === 'mapped');
+
+ // getMappedRange must not change the map state
+ buffer.getMappedRange(...range);
+ t.expect(buffer.mapState === 'mapped');
+ } catch {
+ // unmapped before resolve, destroyed before resolve, or mapAsync validation error
+ // will end up with rejection and 'unmapped'
+ t.expect(buffer.mapState === 'unmapped');
+ }
+ }
+
+ // If buffer is already mapped test mapAsync on already mapped buffer
+ if (buffer.mapState === 'mapped') {
+ // mapAsync on already mapped buffer must be rejected with a validation error
+ // and the map state must keep 'mapped'
+ let promise;
+ t.expectValidationError(() => {
+ promise = buffer.mapAsync(GPUMapMode.WRITE);
+ }, true);
+ t.expect(buffer.mapState === 'mapped');
+
+ try {
+ await promise;
+ t.fail('mapAsync on already mapped buffer must not succeed.');
+ } catch {
+ t.expect(buffer.mapState === 'mapped');
+ }
+ }
+
+ if (afterUnmap) {
+ buffer.unmap();
+ t.expect(buffer.mapState === 'unmapped');
+ }
+
+ if (afterDestroy) {
+ buffer.destroy();
+ t.expect(buffer.mapState === 'unmapped');
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_ArrayBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_ArrayBuffer.spec.js
new file mode 100644
index 0000000000..1a4dab2de9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_ArrayBuffer.spec.js
@@ -0,0 +1,89 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for the behavior of ArrayBuffers returned by getMappedRange.
+
+TODO: Add tests that transfer to another thread instead of just using MessageChannel.
+TODO: Add tests for any other Web APIs that can detach ArrayBuffers.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { timeout } from '../../../../common/util/timeout.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('postMessage').
+desc(
+ `Using postMessage to send a getMappedRange-returned ArrayBuffer throws a TypeError
+ if it was included in the transfer list. Otherwise, it makes a copy.
+ Test combinations of transfer={false, true}, mapMode={read,write}.`
+).
+params((u) =>
+u //
+.combine('transfer', [false, true]).
+combine('mapMode', ['READ', 'WRITE'])
+).
+fn(async (t) => {
+ const { transfer, mapMode } = t.params;
+ const kSize = 1024;
+
+ // Populate initial data.
+ const initialData = new Uint32Array(new ArrayBuffer(kSize));
+ for (let i = 0; i < initialData.length; ++i) {
+ initialData[i] = i;
+ }
+
+ const buf = t.makeBufferWithContents(
+ initialData,
+ mapMode === 'WRITE' ? GPUBufferUsage.MAP_WRITE : GPUBufferUsage.MAP_READ
+ );
+
+ await buf.mapAsync(GPUMapMode[mapMode]);
+ const ab1 = buf.getMappedRange();
+ t.expect(ab1.byteLength === kSize, 'ab1 should have the size of the buffer');
+
+ const mc = new MessageChannel();
+ const ab2Promise = new Promise((resolve) => {
+ mc.port2.onmessage = (ev) => {
+ if (transfer) {
+ t.fail(
+ `postMessage with ab1 in transfer list should not be received. Unexpected message: ${ev.data}`
+ );
+ } else {
+ resolve(ev.data);
+ }
+ };
+ });
+
+ if (transfer) {
+ t.shouldThrow('TypeError', () => mc.port1.postMessage(ab1, [ab1]));
+ // Wait to make sure the postMessage isn't received.
+ await new Promise((resolve) => timeout(resolve, 100));
+ } else {
+ mc.port1.postMessage(ab1);
+ }
+ t.expect(ab1.byteLength === kSize, 'after postMessage, ab1 should not be detached');
+
+ if (!transfer) {
+ const ab2 = await ab2Promise;
+ t.expect(ab2.byteLength === kSize, 'ab2 should be the same size');
+ const ab2Data = new Uint32Array(ab2, 0, initialData.length);
+ // ab2 should have the same initial contents.
+ t.expectOK(checkElementsEqual(ab2Data, initialData));
+
+ // Mutations to ab2 should not be visible in ab1.
+ const ab1Data = new Uint32Array(ab1, 0, initialData.length);
+ const abs2NewData = initialData.slice().reverse();
+ for (let i = 0; i < ab2Data.length; ++i) {
+ ab2Data[i] = abs2NewData[i];
+ }
+ t.expectOK(checkElementsEqual(ab1Data, initialData));
+ t.expectOK(checkElementsEqual(ab2Data, abs2NewData));
+ }
+
+ buf.unmap();
+ t.expect(ab1.byteLength === 0, 'after unmap, ab1 should be detached');
+
+ // Transferring an already-detached ArrayBuffer is a DataCloneError.
+ t.shouldThrow('DataCloneError', () => mc.port1.postMessage(ab1, [ab1]));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_detach.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_detach.spec.js
new file mode 100644
index 0000000000..4d8df26349
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_detach.spec.js
@@ -0,0 +1,79 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+ Tests that TypedArrays created when mapping a GPUBuffer are detached when the
+ buffer is unmapped or destroyed.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { getGPU } from '../../../../common/util/navigator_gpu.js';
+import { assert } from '../../../../common/util/util.js';
+import { GPUConst } from '../../../constants.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('while_mapped').
+desc(
+ `
+ Test that a mapped buffers are able to properly detach.
+ - Tests {mappable, unmappable mapAtCreation, mappable mapAtCreation}
+ - Tests while {mapped, mapped at creation, mapped at creation then unmapped and mapped again}
+ - When {unmap, destroy, unmap && destroy, device.destroy} is called`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('mappedAtCreation', [false, true]).
+combineWithParams([
+{ usage: GPUConst.BufferUsage.COPY_SRC },
+{ usage: GPUConst.BufferUsage.MAP_WRITE | GPUConst.BufferUsage.COPY_SRC },
+{ usage: GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.MAP_READ },
+{
+ usage: GPUConst.BufferUsage.MAP_WRITE | GPUConst.BufferUsage.COPY_SRC,
+ mapMode: GPUConst.MapMode.WRITE
+},
+{
+ usage: GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.MAP_READ,
+ mapMode: GPUConst.MapMode.READ
+}]
+).
+combineWithParams([
+{ unmap: true, destroy: false },
+{ unmap: false, destroy: true },
+{ unmap: true, destroy: true },
+{ unmap: false, destroy: false, deviceDestroy: true }]
+).
+unless((p) => p.mappedAtCreation === false && p.mapMode === undefined)
+).
+fn(async (t) => {
+ const { usage, mapMode, mappedAtCreation, unmap, destroy, deviceDestroy } = t.params;
+
+ let device = t.device;
+ if (deviceDestroy) {
+ const adapter = await getGPU(t.rec).requestAdapter();
+ assert(adapter !== null);
+ device = await adapter.requestDevice();
+ }
+ const buffer = device.createBuffer({
+ size: 4,
+ usage,
+ mappedAtCreation
+ });
+
+ if (mapMode !== undefined) {
+ if (mappedAtCreation) {
+ buffer.unmap();
+ }
+ await buffer.mapAsync(mapMode);
+ }
+
+ const arrayBuffer = buffer.getMappedRange();
+ const view = new Uint8Array(arrayBuffer);
+ t.expect(arrayBuffer.byteLength === 4);
+ t.expect(view.length === 4);
+
+ if (unmap) buffer.unmap();
+ if (destroy) buffer.destroy();
+ if (deviceDestroy) device.destroy();
+
+ t.expect(arrayBuffer.byteLength === 0, 'ArrayBuffer should be detached');
+ t.expect(view.byteLength === 0, 'ArrayBufferView should be detached');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_oom.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_oom.spec.js
new file mode 100644
index 0000000000..73aa5b73f1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/map_oom.spec.js
@@ -0,0 +1,50 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = 'Test out-of-memory conditions creating large mappable/mappedAtCreation buffers.';
+import { kUnitCaseParamsBuilder } from '../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kBufferUsages } from '../../../capability_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { kMaxSafeMultipleOf8 } from '../../../util/math.js';
+
+const oomAndSizeParams = kUnitCaseParamsBuilder.
+combine('oom', [false, true]).
+expand('size', ({ oom }) => {
+ return oom ?
+ [
+ kMaxSafeMultipleOf8,
+ 0x20_0000_0000 // 128 GB
+ ] :
+ [16];
+});
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('mappedAtCreation').
+desc(
+ `Test creating a very large buffer mappedAtCreation buffer should throw a RangeError only
+ because such a large allocation cannot be created when we initialize an active buffer mapping.
+`
+).
+params(
+ oomAndSizeParams //
+ .beginSubcases().
+ combine('usage', kBufferUsages)
+).
+fn((t) => {
+ const { oom, usage, size } = t.params;
+
+ const f = () => t.device.createBuffer({ mappedAtCreation: true, size, usage });
+
+ if (oom) {
+ // getMappedRange is normally valid on OOM buffers, but this one fails because the
+ // (default) range is too large to create the returned ArrayBuffer.
+ t.shouldThrow('RangeError', f);
+ } else {
+ const buffer = f();
+ const mapping = buffer.getMappedRange();
+ t.expect(mapping.byteLength === size, 'Mapping should be successful');
+ buffer.unmap();
+ t.expect(mapping.byteLength === 0, 'Mapping should be detached');
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/mapping_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/mapping_test.js
new file mode 100644
index 0000000000..5f8f20991b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/mapping_test.js
@@ -0,0 +1,39 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../../../common/util/util.js';import { GPUTest } from '../../../gpu_test.js';
+export class MappingTest extends GPUTest {
+ checkMapWrite(
+ buffer,
+ offset,
+ mappedContents,
+ size)
+ {
+ this.checkMapWriteZeroed(mappedContents, size);
+
+ const mappedView = new Uint32Array(mappedContents);
+ const expected = new Uint32Array(new ArrayBuffer(size));
+ assert(mappedView.byteLength === size);
+ for (let i = 0; i < mappedView.length; ++i) {
+ mappedView[i] = expected[i] = i + 1;
+ }
+ buffer.unmap();
+
+ this.expectGPUBufferValuesEqual(buffer, expected, offset);
+ }
+
+ checkMapWriteZeroed(arrayBuffer, expectedSize) {
+ this.expect(arrayBuffer.byteLength === expectedSize);
+ const view = new Uint8Array(arrayBuffer);
+ this.expectZero(view);
+ }
+
+ expectZero(actual) {
+ const size = actual.byteLength;
+ for (let i = 0; i < size; ++i) {
+ if (actual[i] !== 0) {
+ this.fail(`at [${i}], expected zero, got ${actual[i]}`);
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/threading.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/threading.spec.js
new file mode 100644
index 0000000000..a665ad7c28
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/buffers/threading.spec.js
@@ -0,0 +1,29 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for valid operations with various client-side thread-shared state of GPUBuffers.
+
+States to test:
+- mapping pending
+- mapped
+- mapped at creation
+- mapped at creation, then unmapped
+- mapped at creation, then unmapped, then re-mapped
+- destroyed
+
+TODO: Look for more things to test.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('serialize').
+desc(
+ `Copy a GPUBuffer to another thread while it is in various states on
+{the sending thread, yet another thread}.`
+).
+unimplemented();
+
+g.test('destroyed').
+desc(`Destroy on one thread while in various states in another thread.`).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/basic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/basic.spec.js
new file mode 100644
index 0000000000..56b6785a65
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/basic.spec.js
@@ -0,0 +1,98 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Basic tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { memcpy } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('empty').fn((t) => {
+ const encoder = t.device.createCommandEncoder();
+ const cmd = encoder.finish();
+ t.device.queue.submit([cmd]);
+});
+
+g.test('b2t2b').fn((t) => {
+ const data = new Uint32Array([0x01020304]);
+
+ const src = t.device.createBuffer({
+ mappedAtCreation: true,
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ memcpy({ src: data }, { dst: src.getMappedRange() });
+ src.unmap();
+
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const mid = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'rgba8uint',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ { buffer: src, bytesPerRow: 256 },
+ { texture: mid, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ encoder.copyTextureToBuffer(
+ { texture: mid, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(dst, data);
+});
+
+g.test('b2t2t2b').fn((t) => {
+ const data = new Uint32Array([0x01020304]);
+
+ const src = t.device.createBuffer({
+ mappedAtCreation: true,
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ memcpy({ src: data }, { dst: src.getMappedRange() });
+ src.unmap();
+
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const midDesc = {
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'rgba8uint',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ };
+ const mid1 = t.device.createTexture(midDesc);
+ const mid2 = t.device.createTexture(midDesc);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ { buffer: src, bytesPerRow: 256 },
+ { texture: mid1, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ encoder.copyTextureToTexture(
+ { texture: mid1, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { texture: mid2, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ encoder.copyTextureToBuffer(
+ { texture: mid2, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(dst, data);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/clearBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/clearBuffer.spec.js
new file mode 100644
index 0000000000..42a2f63ddd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/clearBuffer.spec.js
@@ -0,0 +1,54 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+API operations tests for clearBuffer.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('clear').
+desc(
+ `Validate the correctness of the clear by filling the srcBuffer with testable data, doing
+ clearBuffer(), and verifying the content of the whole srcBuffer with MapRead:
+ Clear {4 bytes, part of, the whole} buffer {with, without} a non-zero valid offset that
+ - covers the whole buffer
+ - covers the beginning of the buffer
+ - covers the end of the buffer
+ - covers neither the beginning nor the end of the buffer`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('offset', [0, 4, 8, 16, undefined]).
+combine('size', [0, 4, 8, 16, undefined]).
+expand('bufferSize', (p) => [
+(p.offset ?? 0) + (p.size ?? 16),
+(p.offset ?? 0) + (p.size ?? 16) + 8]
+)
+).
+fn((t) => {
+ const { offset, size, bufferSize } = t.params;
+
+ const bufferData = new Uint8Array(bufferSize);
+ for (let i = 0; i < bufferSize; ++i) {
+ bufferData[i] = i + 1;
+ }
+
+ const buffer = t.makeBufferWithContents(
+ bufferData,
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.clearBuffer(buffer, offset, size);
+ t.device.queue.submit([encoder.finish()]);
+
+ const expectOffset = offset ?? 0;
+ const expectSize = size ?? bufferSize - expectOffset;
+
+ for (let i = 0; i < expectSize; ++i) {
+ bufferData[expectOffset + i] = 0;
+ }
+
+ t.expectGPUBufferValuesEqual(buffer, bufferData);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyBufferToBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyBufferToBuffer.spec.js
new file mode 100644
index 0000000000..376596a7b7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyBufferToBuffer.spec.js
@@ -0,0 +1,108 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = 'copyBufferToBuffer operation tests';import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('single').
+desc(
+ `Validate the correctness of the copy by filling the srcBuffer with testable data, doing
+ CopyBufferToBuffer() copy, and verifying the content of the whole dstBuffer with MapRead:
+ Copy {4 bytes, part of, the whole} srcBuffer to the dstBuffer {with, without} a non-zero valid
+ srcOffset that
+ - covers the whole dstBuffer
+ - covers the beginning of the dstBuffer
+ - covers the end of the dstBuffer
+ - covers neither the beginning nor the end of the dstBuffer`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcOffset', [0, 4, 8, 16]).
+combine('dstOffset', [0, 4, 8, 16]).
+combine('copySize', [0, 4, 8, 16]).
+expand('srcBufferSize', (p) => [p.srcOffset + p.copySize, p.srcOffset + p.copySize + 8]).
+expand('dstBufferSize', (p) => [p.dstOffset + p.copySize, p.dstOffset + p.copySize + 8])
+).
+fn((t) => {
+ const { srcOffset, dstOffset, copySize, srcBufferSize, dstBufferSize } = t.params;
+
+ const srcData = new Uint8Array(srcBufferSize);
+ for (let i = 0; i < srcBufferSize; ++i) {
+ srcData[i] = i + 1;
+ }
+
+ const src = t.makeBufferWithContents(srcData, GPUBufferUsage.COPY_SRC);
+
+ const dst = t.device.createBuffer({
+ size: dstBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(dst);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToBuffer(src, srcOffset, dst, dstOffset, copySize);
+ t.device.queue.submit([encoder.finish()]);
+
+ const expectedDstData = new Uint8Array(dstBufferSize);
+ for (let i = 0; i < copySize; ++i) {
+ expectedDstData[dstOffset + i] = srcData[srcOffset + i];
+ }
+
+ t.expectGPUBufferValuesEqual(dst, expectedDstData);
+});
+
+g.test('state_transitions').
+desc(
+ `Test proper state transitions/barriers happen between copy commands.
+ Copy part of src to dst, then a different part of dst to src, and check contents of both.`
+).
+fn((t) => {
+ const srcData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
+ const dstData = new Uint8Array([10, 20, 30, 40, 50, 60, 70, 80]);
+
+ const src = t.makeBufferWithContents(
+ srcData,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ const dst = t.makeBufferWithContents(
+ dstData,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToBuffer(src, 0, dst, 4, 4);
+ encoder.copyBufferToBuffer(dst, 0, src, 4, 4);
+ t.device.queue.submit([encoder.finish()]);
+
+ const expectedSrcData = new Uint8Array([1, 2, 3, 4, 10, 20, 30, 40]);
+ const expectedDstData = new Uint8Array([10, 20, 30, 40, 1, 2, 3, 4]);
+ t.expectGPUBufferValuesEqual(src, expectedSrcData);
+ t.expectGPUBufferValuesEqual(dst, expectedDstData);
+});
+
+g.test('copy_order').
+desc(
+ `Test copy commands in one command buffer occur in the correct order.
+ First copies one region from src to dst, then another region from src to an overlapping region
+ of dst, then checks the dst buffer's contents.`
+).
+fn((t) => {
+ const srcData = new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8]);
+
+ const src = t.makeBufferWithContents(srcData, GPUBufferUsage.COPY_SRC);
+
+ const dst = t.device.createBuffer({
+ size: srcData.length * 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(dst);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToBuffer(src, 0, dst, 0, 16);
+ encoder.copyBufferToBuffer(src, 16, dst, 8, 16);
+ t.device.queue.submit([encoder.finish()]);
+
+ const expectedDstData = new Uint32Array([1, 2, 5, 6, 7, 8, 0, 0]);
+ t.expectGPUBufferValuesEqual(dst, expectedDstData);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.js
new file mode 100644
index 0000000000..bc42d07326
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.js
@@ -0,0 +1,1686 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `copyTextureToTexture operation tests`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, memcpy, unreachable } from '../../../../common/util/util.js';
+import {
+ kBufferSizeAlignment,
+ kMinDynamicBufferOffsetAlignment,
+ kTextureDimensions } from
+'../../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kRegularTextureFormats,
+ kCompressedTextureFormats,
+ kDepthStencilFormats,
+ textureDimensionAndFormatCompatible,
+ depthStencilFormatAspectSize,
+
+
+ isCompressedTextureFormat,
+ viewCompatible } 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 { 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';
+
+const dataGenerator = new DataArrayGenerator();
+
+class F extends TextureTestMixin(GPUTest) {
+ GetInitialDataPerMipLevel(
+ dimension,
+ textureSize,
+ format,
+ mipLevel)
+ {
+ const textureSizeAtLevel = physicalMipSize(textureSize, format, dimension, mipLevel);
+ const bytesPerBlock = kTextureFormatInfo[format].color.bytes;
+ const blockWidthInTexel = kTextureFormatInfo[format].blockWidth;
+ const blockHeightInTexel = kTextureFormatInfo[format].blockHeight;
+ const blocksPerSubresource =
+ textureSizeAtLevel.width / blockWidthInTexel * (
+ textureSizeAtLevel.height / blockHeightInTexel);
+
+ const byteSize = bytesPerBlock * blocksPerSubresource * textureSizeAtLevel.depthOrArrayLayers;
+ return dataGenerator.generateView(byteSize);
+ }
+
+ GetInitialStencilDataPerMipLevel(
+ textureSize,
+ format,
+ mipLevel)
+ {
+ const textureSizeAtLevel = physicalMipSize(textureSize, format, '2d', mipLevel);
+ const aspectBytesPerBlock = depthStencilFormatAspectSize(format, 'stencil-only');
+ const byteSize =
+ aspectBytesPerBlock *
+ textureSizeAtLevel.width *
+ textureSizeAtLevel.height *
+ textureSizeAtLevel.depthOrArrayLayers;
+ return dataGenerator.generateView(byteSize);
+ }
+
+ DoCopyTextureToTextureTest(
+ dimension,
+ srcTextureSize,
+ dstTextureSize,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+
+
+
+
+ srcCopyLevel,
+ dstCopyLevel)
+ {
+ this.skipIfTextureFormatNotSupported(srcFormat, dstFormat);
+
+ // If we're in compatibility mode and it's a compressed texture
+ // then we need to render the texture to test the results of the copy.
+ const extraTextureUsageFlags =
+ isCompressedTextureFormat(dstFormat) && this.isCompatibility ?
+ GPUTextureUsage.TEXTURE_BINDING :
+ 0;
+ const mipLevelCount = dimension === '1d' ? 1 : 4;
+
+ // Create srcTexture and dstTexture
+ const srcTextureDesc = {
+ dimension,
+ size: srcTextureSize,
+ format: srcFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ mipLevelCount
+ };
+ const srcTexture = this.device.createTexture(srcTextureDesc);
+ this.trackForCleanup(srcTexture);
+ const dstTextureDesc = {
+ dimension,
+ size: dstTextureSize,
+ format: dstFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | extraTextureUsageFlags,
+ mipLevelCount
+ };
+ const dstTexture = this.device.createTexture(dstTextureDesc);
+ this.trackForCleanup(dstTexture);
+
+ // Fill the whole subresource of srcTexture at srcCopyLevel with initialSrcData.
+ const initialSrcData = this.GetInitialDataPerMipLevel(
+ dimension,
+ srcTextureSize,
+ srcFormat,
+ srcCopyLevel
+ );
+ const srcTextureSizeAtLevel = physicalMipSize(
+ srcTextureSize,
+ srcFormat,
+ dimension,
+ srcCopyLevel
+ );
+ const bytesPerBlock = kTextureFormatInfo[srcFormat].color.bytes;
+ const blockWidth = kTextureFormatInfo[srcFormat].blockWidth;
+ const blockHeight = kTextureFormatInfo[srcFormat].blockHeight;
+ const srcBlocksPerRow = srcTextureSizeAtLevel.width / blockWidth;
+ const srcBlockRowsPerImage = srcTextureSizeAtLevel.height / blockHeight;
+ this.device.queue.writeTexture(
+ { texture: srcTexture, mipLevel: srcCopyLevel },
+ initialSrcData,
+ {
+ bytesPerRow: srcBlocksPerRow * bytesPerBlock,
+ rowsPerImage: srcBlockRowsPerImage
+ },
+ srcTextureSizeAtLevel
+ );
+
+ // Copy the region specified by copyBoxOffsets from srcTexture to dstTexture.
+ const dstTextureSizeAtLevel = physicalMipSize(
+ dstTextureSize,
+ dstFormat,
+ dimension,
+ dstCopyLevel
+ );
+ const minWidth = Math.min(srcTextureSizeAtLevel.width, dstTextureSizeAtLevel.width);
+ const minHeight = Math.min(srcTextureSizeAtLevel.height, dstTextureSizeAtLevel.height);
+ const minDepth = Math.min(
+ srcTextureSizeAtLevel.depthOrArrayLayers,
+ dstTextureSizeAtLevel.depthOrArrayLayers
+ );
+
+ const appliedSrcOffset = {
+ x: Math.min(copyBoxOffsets.srcOffset.x * blockWidth, minWidth),
+ y: Math.min(copyBoxOffsets.srcOffset.y * blockHeight, minHeight),
+ z: Math.min(copyBoxOffsets.srcOffset.z, minDepth)
+ };
+ const appliedDstOffset = {
+ x: Math.min(copyBoxOffsets.dstOffset.x * blockWidth, minWidth),
+ y: Math.min(copyBoxOffsets.dstOffset.y * blockHeight, minHeight),
+ z: Math.min(copyBoxOffsets.dstOffset.z, minDepth)
+ };
+
+ const appliedCopyWidth = Math.max(
+ minWidth +
+ copyBoxOffsets.copyExtent.width * blockWidth -
+ Math.max(appliedSrcOffset.x, appliedDstOffset.x),
+ 0
+ );
+ const appliedCopyHeight = Math.max(
+ minHeight +
+ copyBoxOffsets.copyExtent.height * blockHeight -
+ Math.max(appliedSrcOffset.y, appliedDstOffset.y),
+ 0
+ );
+ assert(appliedCopyWidth % blockWidth === 0 && appliedCopyHeight % blockHeight === 0);
+
+ const appliedCopyDepth = Math.max(
+ 0,
+ minDepth +
+ copyBoxOffsets.copyExtent.depthOrArrayLayers -
+ Math.max(appliedSrcOffset.z, appliedDstOffset.z)
+ );
+ assert(appliedCopyDepth >= 0);
+
+ const appliedSize = {
+ width: appliedCopyWidth,
+ height: appliedCopyHeight,
+ depthOrArrayLayers: appliedCopyDepth
+ };
+
+ {
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToTexture(
+ { texture: srcTexture, mipLevel: srcCopyLevel, origin: appliedSrcOffset },
+ { texture: dstTexture, mipLevel: dstCopyLevel, origin: appliedDstOffset },
+ appliedSize
+ );
+ this.device.queue.submit([encoder.finish()]);
+ }
+
+ const dstBlocksPerRow = dstTextureSizeAtLevel.width / blockWidth;
+ const dstBlockRowsPerImage = dstTextureSizeAtLevel.height / blockHeight;
+ const bytesPerDstAlignedBlockRow = align(dstBlocksPerRow * bytesPerBlock, 256);
+ const dstBufferSize =
+ (dstBlockRowsPerImage * dstTextureSizeAtLevel.depthOrArrayLayers - 1) *
+ bytesPerDstAlignedBlockRow +
+ align(dstBlocksPerRow * bytesPerBlock, 4);
+
+ if (isCompressedTextureFormat(dstTexture.format) && this.isCompatibility) {
+ assert(viewCompatible(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.
+ const expectedTexture = this.device.createTexture({
+ size: [dstTexture.width, dstTexture.height, dstTexture.depthOrArrayLayers],
+ mipLevelCount: dstTexture.mipLevelCount,
+ format: dstTexture.format,
+ usage: dstTexture.usage
+ });
+ const expectedData = new Uint8Array(dstBufferSize);
+
+ // Execute the equivalent of `copyTextureToTexture`, copying
+ // from `initialSrcData` to `expectedData`.
+ this.updateLinearTextureDataSubBox(dstFormat, appliedSize, {
+ src: {
+ dataLayout: {
+ bytesPerRow: srcBlocksPerRow * bytesPerBlock,
+ rowsPerImage: srcBlockRowsPerImage,
+ offset: 0
+ },
+ origin: appliedSrcOffset,
+ data: initialSrcData
+ },
+ dest: {
+ dataLayout: {
+ bytesPerRow: dstBlocksPerRow * bytesPerBlock,
+ rowsPerImage: dstBlockRowsPerImage,
+ offset: 0
+ },
+ origin: appliedDstOffset,
+ data: expectedData
+ }
+ });
+
+ // Upload `expectedData` to `expectedTexture`. If `copyTextureToTexture`
+ // worked then the contents of `dstTexture` should match `expectedTexture`
+ this.queue.writeTexture(
+ { texture: expectedTexture, mipLevel: dstCopyLevel },
+ expectedData,
+ {
+ bytesPerRow: dstBlocksPerRow * bytesPerBlock,
+ rowsPerImage: dstBlockRowsPerImage
+ },
+ dstTextureSizeAtLevel
+ );
+
+ this.expectTexturesToMatchByRendering(
+ dstTexture,
+ expectedTexture,
+ dstCopyLevel,
+ appliedDstOffset,
+ appliedSize
+ );
+ return;
+ }
+
+ // Copy the whole content of dstTexture at dstCopyLevel to dstBuffer.
+ const dstBufferDesc = {
+ size: dstBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ };
+ const dstBuffer = this.device.createBuffer(dstBufferDesc);
+ this.trackForCleanup(dstBuffer);
+
+ {
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ { texture: dstTexture, mipLevel: dstCopyLevel },
+ {
+ buffer: dstBuffer,
+ bytesPerRow: bytesPerDstAlignedBlockRow,
+ rowsPerImage: dstBlockRowsPerImage
+ },
+ dstTextureSizeAtLevel
+ );
+ this.device.queue.submit([encoder.finish()]);
+ }
+
+ // Fill expectedUint8DataWithPadding with the expected data of dstTexture. The other values in
+ // expectedUint8DataWithPadding are kept 0 to check if the texels untouched by the copy are 0
+ // (their previous values).
+ const expectedUint8DataWithPadding = new Uint8Array(dstBufferSize);
+ const expectedUint8Data = new Uint8Array(initialSrcData);
+
+ const appliedCopyBlocksPerRow = appliedCopyWidth / blockWidth;
+ const appliedCopyBlockRowsPerImage = appliedCopyHeight / blockHeight;
+ const srcCopyOffsetInBlocks = {
+ x: appliedSrcOffset.x / blockWidth,
+ y: appliedSrcOffset.y / blockHeight,
+ z: appliedSrcOffset.z
+ };
+ const dstCopyOffsetInBlocks = {
+ x: appliedDstOffset.x / blockWidth,
+ y: appliedDstOffset.y / blockHeight,
+ z: appliedDstOffset.z
+ };
+
+ for (let z = 0; z < appliedCopyDepth; ++z) {
+ const srcOffsetZ = srcCopyOffsetInBlocks.z + z;
+ const dstOffsetZ = dstCopyOffsetInBlocks.z + z;
+ for (let y = 0; y < appliedCopyBlockRowsPerImage; ++y) {
+ const dstOffsetYInBlocks = dstCopyOffsetInBlocks.y + y;
+ const expectedDataWithPaddingOffset =
+ bytesPerDstAlignedBlockRow * (dstBlockRowsPerImage * dstOffsetZ + dstOffsetYInBlocks) +
+ dstCopyOffsetInBlocks.x * bytesPerBlock;
+
+ const srcOffsetYInBlocks = srcCopyOffsetInBlocks.y + y;
+ const expectedDataOffset =
+ bytesPerBlock *
+ srcBlocksPerRow * (
+ srcBlockRowsPerImage * srcOffsetZ + srcOffsetYInBlocks) +
+ srcCopyOffsetInBlocks.x * bytesPerBlock;
+
+ const bytesInRow = appliedCopyBlocksPerRow * bytesPerBlock;
+ memcpy(
+ { src: expectedUint8Data, start: expectedDataOffset, length: bytesInRow },
+ { dst: expectedUint8DataWithPadding, start: expectedDataWithPaddingOffset }
+ );
+ }
+ }
+
+ 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();
+ }
+ }
+
+ // 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
+ }
+ );
+ }
+
+ InitializeStencilAspect(
+ sourceTexture,
+ initialStencilData,
+ srcCopyLevel,
+ srcCopyBaseArrayLayer,
+ copySize)
+ {
+ this.queue.writeTexture(
+ {
+ texture: sourceTexture,
+ mipLevel: srcCopyLevel,
+ aspect: 'stencil-only',
+ origin: { x: 0, y: 0, z: srcCopyBaseArrayLayer }
+ },
+ initialStencilData,
+ { bytesPerRow: copySize[0], rowsPerImage: copySize[1] },
+ copySize
+ );
+ }
+
+ VerifyStencilAspect(
+ destinationTexture,
+ initialStencilData,
+ dstCopyLevel,
+ dstCopyBaseArrayLayer,
+ copySize)
+ {
+ const bytesPerRow = align(copySize[0], kBytesPerRowAlignment);
+ const rowsPerImage = copySize[1];
+ const outputBufferSize = align(
+ dataBytesForCopyOrFail({
+ layout: { bytesPerRow, rowsPerImage },
+ format: 'stencil8',
+ copySize,
+ method: 'CopyT2B'
+ }),
+ kBufferSizeAlignment
+ );
+ const outputBuffer = this.device.createBuffer({
+ size: outputBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(outputBuffer);
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ {
+ texture: destinationTexture,
+ aspect: 'stencil-only',
+ mipLevel: dstCopyLevel,
+ origin: { x: 0, y: 0, z: dstCopyBaseArrayLayer }
+ },
+ { buffer: outputBuffer, bytesPerRow, rowsPerImage },
+ copySize
+ );
+ this.queue.submit([encoder.finish()]);
+
+ const expectedStencilData = new Uint8Array(outputBufferSize);
+ for (let z = 0; z < copySize[2]; ++z) {
+ const initialOffsetPerLayer = z * copySize[0] * copySize[1];
+ const expectedOffsetPerLayer = z * bytesPerRow * rowsPerImage;
+ for (let y = 0; y < copySize[1]; ++y) {
+ const initialOffsetPerRow = initialOffsetPerLayer + y * copySize[0];
+ const expectedOffsetPerRow = expectedOffsetPerLayer + y * bytesPerRow;
+ memcpy(
+ { src: initialStencilData, start: initialOffsetPerRow, length: copySize[0] },
+ { dst: expectedStencilData, start: expectedOffsetPerRow }
+ );
+ }
+ }
+ this.expectGPUBufferValuesEqual(outputBuffer, expectedStencilData);
+ }
+
+ GetRenderPipelineForT2TCopyWithDepthTests(
+ bindGroupLayout,
+ hasColorAttachment,
+ depthStencil)
+ {
+ const renderPipelineDescriptor = {
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ struct Params {
+ copyLayer: f32
+ };
+ @group(0) @binding(0) var<uniform> param: Params;
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
+ var depthValue = 0.5 + 0.2 * sin(param.copyLayer);
+ var pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
+ vec3<f32>(-1.0, 1.0, depthValue),
+ vec3<f32>(-1.0, -1.0, 0.0),
+ vec3<f32>( 1.0, 1.0, 1.0),
+ vec3<f32>(-1.0, -1.0, 0.0),
+ vec3<f32>( 1.0, 1.0, 1.0),
+ vec3<f32>( 1.0, -1.0, depthValue));
+ return vec4<f32>(pos[VertexIndex], 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ depthStencil
+ };
+ if (hasColorAttachment) {
+ renderPipelineDescriptor.fragment = {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ };
+ }
+ return this.device.createRenderPipeline(renderPipelineDescriptor);
+ }
+
+ GetBindGroupLayoutForT2TCopyWithDepthTests() {
+ return this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {
+ type: 'uniform',
+ minBindingSize: 4,
+ hasDynamicOffset: true
+ }
+ }]
+
+ });
+ }
+
+ GetBindGroupForT2TCopyWithDepthTests(
+ bindGroupLayout,
+ totalCopyArrayLayers)
+ {
+ // Prepare the uniform buffer that contains all the copy layers to generate different depth
+ // values for different copy layers.
+ assert(totalCopyArrayLayers > 0);
+ const uniformBufferSize = kMinDynamicBufferOffsetAlignment * (totalCopyArrayLayers - 1) + 4;
+ const uniformBufferData = new Float32Array(uniformBufferSize / 4);
+ for (let i = 1; i < totalCopyArrayLayers; ++i) {
+ uniformBufferData[kMinDynamicBufferOffsetAlignment / 4 * i] = i;
+ }
+ const uniformBuffer = makeBufferWithContents(
+ this.device,
+ uniformBufferData,
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM
+ );
+ return this.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: uniformBuffer,
+ size: 4
+ }
+ }]
+
+ });
+ }
+
+ /** Initialize the depth aspect of sourceTexture with draw calls */
+ InitializeDepthAspect(
+ sourceTexture,
+ depthFormat,
+ srcCopyLevel,
+ srcCopyBaseArrayLayer,
+ copySize)
+ {
+ // Prepare a renderPipeline with depthCompareFunction == 'always' and depthWriteEnabled == true
+ // for the initializations of the depth attachment.
+ const bindGroupLayout = this.GetBindGroupLayoutForT2TCopyWithDepthTests();
+ const renderPipeline = this.GetRenderPipelineForT2TCopyWithDepthTests(bindGroupLayout, false, {
+ format: depthFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'always'
+ });
+ const bindGroup = this.GetBindGroupForT2TCopyWithDepthTests(bindGroupLayout, copySize[2]);
+
+ const hasStencil = kTextureFormatInfo[sourceTexture.format].stencil;
+ const encoder = this.device.createCommandEncoder();
+ for (let srcCopyLayer = 0; srcCopyLayer < copySize[2]; ++srcCopyLayer) {
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: sourceTexture.createView({
+ baseArrayLayer: srcCopyLayer + srcCopyBaseArrayLayer,
+ arrayLayerCount: 1,
+ baseMipLevel: srcCopyLevel,
+ mipLevelCount: 1
+ }),
+ depthClearValue: 0.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilLoadOp: hasStencil ? 'load' : undefined,
+ stencilStoreOp: hasStencil ? 'store' : undefined
+ }
+ });
+ renderPass.setBindGroup(0, bindGroup, [srcCopyLayer * kMinDynamicBufferOffsetAlignment]);
+ renderPass.setPipeline(renderPipeline);
+ renderPass.draw(6);
+ renderPass.end();
+ }
+ this.queue.submit([encoder.finish()]);
+ }
+
+ VerifyDepthAspect(
+ destinationTexture,
+ depthFormat,
+ dstCopyLevel,
+ dstCopyBaseArrayLayer,
+ copySize)
+ {
+ // Prepare a renderPipeline with depthCompareFunction == 'equal' and depthWriteEnabled == false
+ // for the comparison of the depth attachment.
+ const bindGroupLayout = this.GetBindGroupLayoutForT2TCopyWithDepthTests();
+ const renderPipeline = this.GetRenderPipelineForT2TCopyWithDepthTests(bindGroupLayout, true, {
+ format: depthFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'equal'
+ });
+ const bindGroup = this.GetBindGroupForT2TCopyWithDepthTests(bindGroupLayout, copySize[2]);
+
+ const outputColorTexture = this.trackForCleanup(
+ this.device.createTexture({
+ format: 'rgba8unorm',
+ size: copySize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ })
+ );
+ const hasStencil = kTextureFormatInfo[destinationTexture.format].stencil;
+ const encoder = this.device.createCommandEncoder();
+ for (let dstCopyLayer = 0; dstCopyLayer < copySize[2]; ++dstCopyLayer) {
+ // If the depth value is not expected, the color of outputColorTexture will remain Red after
+ // the render pass.
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputColorTexture.createView({
+ baseArrayLayer: dstCopyLayer,
+ arrayLayerCount: 1
+ }),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment: {
+ view: destinationTexture.createView({
+ baseArrayLayer: dstCopyLayer + dstCopyBaseArrayLayer,
+ arrayLayerCount: 1,
+ baseMipLevel: dstCopyLevel,
+ mipLevelCount: 1
+ }),
+ depthLoadOp: 'load',
+ depthStoreOp: 'store',
+ stencilLoadOp: hasStencil ? 'load' : undefined,
+ stencilStoreOp: hasStencil ? 'store' : undefined
+ }
+ });
+ renderPass.setBindGroup(0, bindGroup, [dstCopyLayer * kMinDynamicBufferOffsetAlignment]);
+ renderPass.setPipeline(renderPipeline);
+ renderPass.draw(6);
+ renderPass.end();
+ }
+ this.queue.submit([encoder.finish()]);
+
+ this.expectSingleColor(outputColorTexture, 'rgba8unorm', {
+ size: copySize,
+ exp: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 }
+ });
+ }
+}
+
+const kCopyBoxOffsetsForWholeDepth = [
+// From (0, 0) of src to (0, 0) of dst.
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+},
+// From (0, 0) of src to (blockWidth, 0) of dst.
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 1, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+},
+// From (0, 0) of src to (0, blockHeight) of dst.
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 1, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+},
+// From (blockWidth, 0) of src to (0, 0) of dst.
+{
+ srcOffset: { x: 1, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+},
+// From (0, blockHeight) of src to (0, 0) of dst.
+{
+ srcOffset: { x: 0, y: 1, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+},
+// From (blockWidth, 0) of src to (0, 0) of dst, and the copy extent will not cover the last
+// texel block column of both source and destination texture.
+{
+ srcOffset: { x: 1, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: -1, height: 0, depthOrArrayLayers: 0 }
+},
+// From (0, blockHeight) of src to (0, 0) of dst, and the copy extent will not cover the last
+// texel block row of both source and destination texture.
+{
+ srcOffset: { x: 0, y: 1, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: -1, depthOrArrayLayers: 0 }
+}];
+
+
+const kCopyBoxOffsetsFor2DArrayTextures = [
+// Copy the whole array slices from the source texture to the destination texture.
+// The copy extent will cover the whole subresource of either source or the
+// destination texture
+...kCopyBoxOffsetsForWholeDepth,
+
+// Copy 1 texture slice from the 1st slice of the source texture to the 1st slice of the
+// destination texture.
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -2 }
+},
+// Copy 1 texture slice from the 2nd slice of the source texture to the 2nd slice of the
+// destination texture.
+{
+ srcOffset: { x: 0, y: 0, z: 1 },
+ dstOffset: { x: 0, y: 0, z: 1 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -3 }
+},
+// Copy 1 texture slice from the 1st slice of the source texture to the 2nd slice of the
+// destination texture.
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 1 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -1 }
+},
+// Copy 1 texture slice from the 2nd slice of the source texture to the 1st slice of the
+// destination texture.
+{
+ srcOffset: { x: 0, y: 0, z: 1 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -1 }
+},
+// Copy 2 texture slices from the 1st slice of the source texture to the 1st slice of the
+// destination texture.
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -3 }
+},
+// Copy 3 texture slices from the 2nd slice of the source texture to the 2nd slice of the
+// destination texture.
+{
+ srcOffset: { x: 0, y: 0, z: 1 },
+ dstOffset: { x: 0, y: 0, z: 1 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -1 }
+}];
+
+
+export const g = makeTestGroup(F);
+
+g.test('color_textures,non_compressed,non_array').
+desc(
+ `
+ Validate the correctness of the copy by filling the srcTexture with testable data and any
+ non-compressed color format supported by WebGPU, doing CopyTextureToTexture() copy, and verifying
+ the content of the whole dstTexture.
+
+ Copy {1 texel block, part of, the whole} srcTexture to the dstTexture {with, without} a non-zero
+ valid srcOffset that
+ - covers the whole dstTexture subresource
+ - covers the corners of the dstTexture
+ - doesn't cover any texels that are on the edge of the dstTexture
+ - covers the mipmap level > 0
+
+ Tests for all pairs of valid source/destination formats, and all texture dimensions.
+ `
+).
+params((u) =>
+u.
+combine('srcFormat', kRegularTextureFormats).
+combine('dstFormat', kRegularTextureFormats).
+filter(({ srcFormat, dstFormat }) => {
+ const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
+ const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
+ return (
+ srcFormat === dstFormat ||
+ srcBaseFormat !== undefined &&
+ dstBaseFormat !== undefined &&
+ srcBaseFormat === dstBaseFormat);
+
+}).
+combine('dimension', kTextureDimensions).
+filter(
+ ({ dimension, srcFormat, dstFormat }) =>
+ textureDimensionAndFormatCompatible(dimension, srcFormat) &&
+ textureDimensionAndFormatCompatible(dimension, dstFormat)
+).
+beginSubcases().
+expandWithParams((p) => {
+ const params = [
+ {
+ srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
+ dstTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 }
+ },
+ {
+ srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 },
+ dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 1 }
+ },
+ {
+ srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
+ dstTextureSize: { width: 64, height: 64, depthOrArrayLayers: 1 }
+ },
+ {
+ srcTextureSize: { width: 32, height: 32, depthOrArrayLayers: 1 },
+ dstTextureSize: { width: 63, height: 61, depthOrArrayLayers: 1 }
+ }];
+
+ if (p.dimension === '1d') {
+ for (const param of params) {
+ param.srcTextureSize.height = 1;
+ param.dstTextureSize.height = 1;
+ }
+ }
+
+ return params;
+}).
+combine('copyBoxOffsets', kCopyBoxOffsetsForWholeDepth).
+unless(
+ (p) =>
+ p.dimension === '1d' && (
+ p.copyBoxOffsets.copyExtent.height !== 0 ||
+ p.copyBoxOffsets.srcOffset.y !== 0 ||
+ p.copyBoxOffsets.dstOffset.y !== 0)
+).
+combine('srcCopyLevel', [0, 3]).
+combine('dstCopyLevel', [0, 3]).
+unless((p) => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0))
+).
+fn((t) => {
+ const {
+ dimension,
+ srcTextureSize,
+ dstTextureSize,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ } = t.params;
+
+ t.DoCopyTextureToTextureTest(
+ dimension,
+ srcTextureSize,
+ dstTextureSize,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ );
+});
+
+g.test('color_textures,compressed,non_array').
+desc(
+ `
+ Validate the correctness of the copy by filling the srcTexture with testable data and any
+ compressed color format supported by WebGPU, doing CopyTextureToTexture() copy, and verifying
+ the content of the whole dstTexture.
+
+ Tests for all pairs of valid source/destination formats, and all texture dimensions.
+ `
+).
+params((u) =>
+u.
+combine('srcFormat', kCompressedTextureFormats).
+combine('dstFormat', kCompressedTextureFormats).
+filter(({ srcFormat, dstFormat }) => {
+ const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
+ const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
+ return (
+ srcFormat === dstFormat ||
+ srcBaseFormat !== undefined &&
+ dstBaseFormat !== undefined &&
+ srcBaseFormat === dstBaseFormat);
+
+}).
+combine('dimension', kTextureDimensions).
+filter(
+ ({ dimension, srcFormat, dstFormat }) =>
+ textureDimensionAndFormatCompatible(dimension, srcFormat) &&
+ textureDimensionAndFormatCompatible(dimension, dstFormat)
+).
+beginSubcases().
+combine('textureSizeInBlocks', [
+// The heights and widths in blocks are all power of 2
+{ src: { width: 16, height: 8 }, dst: { width: 16, height: 8 } },
+// The virtual width of the source texture at mipmap level 2 (15) is not a multiple of 4 blocks
+{ src: { width: 15, height: 8 }, dst: { width: 16, height: 8 } },
+// The virtual width of the destination texture at mipmap level 2 (15) is not a multiple
+// of 4 blocks
+{ src: { width: 16, height: 8 }, dst: { width: 15, height: 8 } },
+// The virtual height of the source texture at mipmap level 2 (13) is not a multiple of 4 blocks
+{ src: { width: 16, height: 13 }, dst: { width: 16, height: 8 } },
+// The virtual height of the destination texture at mipmap level 2 (13) is not a
+// multiple of 4 blocks
+{ src: { width: 16, height: 8 }, dst: { width: 16, height: 13 } },
+// None of the widths or heights in blocks are power of 2
+{ src: { width: 15, height: 13 }, dst: { width: 15, height: 13 } }]
+).
+combine('copyBoxOffsets', kCopyBoxOffsetsForWholeDepth).
+combine('srcCopyLevel', [0, 2]).
+combine('dstCopyLevel', [0, 2])
+).
+beforeAllSubcases((t) => {
+ const { srcFormat, dstFormat } = t.params;
+ t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
+ t.selectDeviceOrSkipTestCase([
+ kTextureFormatInfo[srcFormat].feature,
+ kTextureFormatInfo[dstFormat].feature]
+ );
+}).
+fn((t) => {
+ const {
+ dimension,
+ textureSizeInBlocks,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ } = t.params;
+ const srcBlockWidth = kTextureFormatInfo[srcFormat].blockWidth;
+ const srcBlockHeight = kTextureFormatInfo[srcFormat].blockHeight;
+ const dstBlockWidth = kTextureFormatInfo[dstFormat].blockWidth;
+ const dstBlockHeight = kTextureFormatInfo[dstFormat].blockHeight;
+
+ t.DoCopyTextureToTextureTest(
+ dimension,
+ {
+ width: textureSizeInBlocks.src.width * srcBlockWidth,
+ height: textureSizeInBlocks.src.height * srcBlockHeight,
+ depthOrArrayLayers: 1
+ },
+ {
+ width: textureSizeInBlocks.dst.width * dstBlockWidth,
+ height: textureSizeInBlocks.dst.height * dstBlockHeight,
+ depthOrArrayLayers: 1
+ },
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ );
+});
+
+g.test('color_textures,non_compressed,array').
+desc(
+ `
+ Validate the correctness of the texture-to-texture copy on 2D array textures by filling the
+ srcTexture with testable data and any non-compressed color format supported by WebGPU, doing
+ CopyTextureToTexture() copy, and verifying the content of the whole dstTexture.
+ `
+).
+params((u) =>
+u.
+combine('srcFormat', kRegularTextureFormats).
+combine('dstFormat', kRegularTextureFormats).
+filter(({ srcFormat, dstFormat }) => {
+ const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
+ const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
+ return (
+ srcFormat === dstFormat ||
+ srcBaseFormat !== undefined &&
+ dstBaseFormat !== undefined &&
+ srcBaseFormat === dstBaseFormat);
+
+}).
+combine('dimension', ['2d', '3d']).
+filter(
+ ({ dimension, srcFormat, dstFormat }) =>
+ textureDimensionAndFormatCompatible(dimension, srcFormat) &&
+ textureDimensionAndFormatCompatible(dimension, dstFormat)
+).
+beginSubcases().
+combine('textureSize', [
+{
+ srcTextureSize: { width: 64, height: 32, depthOrArrayLayers: 5 },
+ dstTextureSize: { width: 64, height: 32, depthOrArrayLayers: 5 }
+},
+{
+ srcTextureSize: { width: 31, height: 33, depthOrArrayLayers: 5 },
+ dstTextureSize: { width: 31, height: 33, depthOrArrayLayers: 5 }
+},
+{
+ srcTextureSize: { width: 31, height: 32, depthOrArrayLayers: 33 },
+ dstTextureSize: { width: 31, height: 32, depthOrArrayLayers: 33 }
+}]
+).
+
+combine('copyBoxOffsets', kCopyBoxOffsetsFor2DArrayTextures).
+combine('srcCopyLevel', [0, 3]).
+combine('dstCopyLevel', [0, 3])
+).
+fn((t) => {
+ const {
+ dimension,
+ textureSize,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ } = t.params;
+
+ t.DoCopyTextureToTextureTest(
+ dimension,
+ textureSize.srcTextureSize,
+ textureSize.dstTextureSize,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ );
+});
+
+g.test('color_textures,compressed,array').
+desc(
+ `
+ Validate the correctness of the texture-to-texture copy on 2D array textures by filling the
+ srcTexture with testable data and any compressed color format supported by WebGPU, doing
+ CopyTextureToTexture() copy, and verifying the content of the whole dstTexture.
+
+ Tests for all pairs of valid source/destination formats, and all texture dimensions.
+ `
+).
+params((u) =>
+u.
+combine('srcFormat', kCompressedTextureFormats).
+combine('dstFormat', kCompressedTextureFormats).
+filter(({ srcFormat, dstFormat }) => {
+ const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat;
+ const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat;
+ return (
+ srcFormat === dstFormat ||
+ srcBaseFormat !== undefined &&
+ dstBaseFormat !== undefined &&
+ srcBaseFormat === dstBaseFormat);
+
+}).
+combine('dimension', ['2d', '3d']).
+filter(
+ ({ dimension, srcFormat, dstFormat }) =>
+ textureDimensionAndFormatCompatible(dimension, srcFormat) &&
+ textureDimensionAndFormatCompatible(dimension, dstFormat)
+).
+beginSubcases().
+combine('textureSizeInBlocks', [
+// The heights and widths in blocks are all power of 2
+{ src: { width: 2, height: 2 }, dst: { width: 2, height: 2 } },
+// None of the widths or heights in blocks are power of 2
+{ src: { width: 15, height: 13 }, dst: { width: 15, height: 13 } }]
+).
+combine('copyBoxOffsets', kCopyBoxOffsetsFor2DArrayTextures).
+combine('srcCopyLevel', [0, 2]).
+combine('dstCopyLevel', [0, 2])
+).
+beforeAllSubcases((t) => {
+ const { srcFormat, dstFormat } = t.params;
+ t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
+ t.selectDeviceOrSkipTestCase([
+ kTextureFormatInfo[srcFormat].feature,
+ kTextureFormatInfo[dstFormat].feature]
+ );
+}).
+fn((t) => {
+ const {
+ dimension,
+ textureSizeInBlocks,
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ } = t.params;
+ const srcBlockWidth = kTextureFormatInfo[srcFormat].blockWidth;
+ const srcBlockHeight = kTextureFormatInfo[srcFormat].blockHeight;
+ const dstBlockWidth = kTextureFormatInfo[dstFormat].blockWidth;
+ const dstBlockHeight = kTextureFormatInfo[dstFormat].blockHeight;
+
+ t.DoCopyTextureToTextureTest(
+ dimension,
+ {
+ width: textureSizeInBlocks.src.width * srcBlockWidth,
+ height: textureSizeInBlocks.src.height * srcBlockHeight,
+ depthOrArrayLayers: 5
+ },
+ {
+ width: textureSizeInBlocks.dst.width * dstBlockWidth,
+ height: textureSizeInBlocks.dst.height * dstBlockHeight,
+ depthOrArrayLayers: 5
+ },
+ srcFormat,
+ dstFormat,
+ copyBoxOffsets,
+ srcCopyLevel,
+ dstCopyLevel
+ );
+});
+
+g.test('zero_sized').
+desc(
+ `
+ Validate the correctness of zero-sized copies (should be no-ops).
+
+ - For each texture dimension.
+ - Copies that are zero-sized in only one dimension {x, y, z}, each touching the {lower, upper} end
+ of that dimension.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combineWithParams([
+{ dimension: '1d', textureSize: { width: 32, height: 1, depthOrArrayLayers: 1 } },
+{ dimension: '2d', textureSize: { width: 32, height: 32, depthOrArrayLayers: 5 } },
+{ dimension: '3d', textureSize: { width: 32, height: 32, depthOrArrayLayers: 5 } }]
+).
+combine('copyBoxOffset', [
+// copyExtent.width === 0
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: -64, height: 0, depthOrArrayLayers: 0 }
+},
+// copyExtent.width === 0 && srcOffset.x === textureWidth
+{
+ srcOffset: { x: 64, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: -64, height: 0, depthOrArrayLayers: 0 }
+},
+// copyExtent.width === 0 && dstOffset.x === textureWidth
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 64, y: 0, z: 0 },
+ copyExtent: { width: -64, height: 0, depthOrArrayLayers: 0 }
+},
+// copyExtent.height === 0
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: -32, depthOrArrayLayers: 0 }
+},
+// copyExtent.height === 0 && srcOffset.y === textureHeight
+{
+ srcOffset: { x: 0, y: 32, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: -32, depthOrArrayLayers: 0 }
+},
+// copyExtent.height === 0 && dstOffset.y === textureHeight
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 32, z: 0 },
+ copyExtent: { width: 0, height: -32, depthOrArrayLayers: 0 }
+},
+// copyExtent.depthOrArrayLayers === 0
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: -5 }
+},
+// copyExtent.depthOrArrayLayers === 0 && srcOffset.z === textureDepth
+{
+ srcOffset: { x: 0, y: 0, z: 5 },
+ dstOffset: { x: 0, y: 0, z: 0 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+},
+// copyExtent.depthOrArrayLayers === 0 && dstOffset.z === textureDepth
+{
+ srcOffset: { x: 0, y: 0, z: 0 },
+ dstOffset: { x: 0, y: 0, z: 5 },
+ copyExtent: { width: 0, height: 0, depthOrArrayLayers: 0 }
+}]
+).
+unless(
+ (p) =>
+ p.dimension === '1d' && (
+ p.copyBoxOffset.copyExtent.height !== 0 ||
+ p.copyBoxOffset.srcOffset.y !== 0 ||
+ p.copyBoxOffset.dstOffset.y !== 0)
+).
+combine('srcCopyLevel', [0, 3]).
+combine('dstCopyLevel', [0, 3]).
+unless((p) => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0))
+).
+fn((t) => {
+ const { dimension, textureSize, copyBoxOffset, srcCopyLevel, dstCopyLevel } = t.params;
+
+ const srcFormat = 'rgba8unorm';
+ const dstFormat = 'rgba8unorm';
+
+ t.DoCopyTextureToTextureTest(
+ dimension,
+ textureSize,
+ textureSize,
+ srcFormat,
+ dstFormat,
+ copyBoxOffset,
+ srcCopyLevel,
+ dstCopyLevel
+ );
+});
+
+g.test('copy_depth_stencil').
+desc(
+ `
+ Validate the correctness of copyTextureToTexture() with depth and stencil aspect.
+
+ For all the texture formats with stencil aspect:
+ - Initialize the stencil aspect of the source texture with writeTexture().
+ - Copy the stencil aspect from the source texture into the destination texture
+ - Copy the stencil aspect of the destination texture into another staging buffer and check its
+ content
+ - Test the copies from / into zero / non-zero array layer / mipmap levels
+ - Test copying multiple array layers
+
+ For all the texture formats with depth aspect:
+ - Initialize the depth aspect of the source texture with a draw call
+ - Copy the depth aspect from the source texture into the destination texture
+ - Validate the content in the destination texture with the depth comparison function 'equal'
+ `
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+beginSubcases().
+combine('srcTextureSize', [
+{ width: 32, height: 16, depthOrArrayLayers: 1 },
+{ width: 32, height: 16, depthOrArrayLayers: 4 },
+{ width: 24, height: 48, depthOrArrayLayers: 5 }]
+).
+combine('srcCopyLevel', [0, 2]).
+combine('dstCopyLevel', [0, 2]).
+combine('srcCopyBaseArrayLayer', [0, 1]).
+combine('dstCopyBaseArrayLayer', [0, 1]).
+filter((t) => {
+ return (
+ t.srcTextureSize.depthOrArrayLayers > t.srcCopyBaseArrayLayer &&
+ t.srcTextureSize.depthOrArrayLayers > t.dstCopyBaseArrayLayer);
+
+})
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(format);
+}).
+fn((t) => {
+ const {
+ format,
+ srcTextureSize,
+ srcCopyLevel,
+ dstCopyLevel,
+ srcCopyBaseArrayLayer,
+ dstCopyBaseArrayLayer
+ } = t.params;
+
+ const copySize = [
+ srcTextureSize.width >> srcCopyLevel,
+ srcTextureSize.height >> srcCopyLevel,
+ srcTextureSize.depthOrArrayLayers - Math.max(srcCopyBaseArrayLayer, dstCopyBaseArrayLayer)];
+
+ const sourceTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: srcTextureSize,
+ usage:
+ GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ mipLevelCount: srcCopyLevel + 1
+ })
+ );
+ const destinationTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [
+ copySize[0] << dstCopyLevel,
+ copySize[1] << dstCopyLevel,
+ srcTextureSize.depthOrArrayLayers],
+
+ usage:
+ GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ mipLevelCount: dstCopyLevel + 1
+ })
+ );
+
+ let initialStencilData = undefined;
+ if (kTextureFormatInfo[format].stencil) {
+ initialStencilData = t.GetInitialStencilDataPerMipLevel(srcTextureSize, format, srcCopyLevel);
+ t.InitializeStencilAspect(
+ sourceTexture,
+ initialStencilData,
+ srcCopyLevel,
+ srcCopyBaseArrayLayer,
+ copySize
+ );
+ }
+ if (kTextureFormatInfo[format].depth) {
+ t.InitializeDepthAspect(sourceTexture, format, srcCopyLevel, srcCopyBaseArrayLayer, copySize);
+ }
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture(
+ {
+ texture: sourceTexture,
+ mipLevel: srcCopyLevel,
+ origin: { x: 0, y: 0, z: srcCopyBaseArrayLayer }
+ },
+ {
+ texture: destinationTexture,
+ mipLevel: dstCopyLevel,
+ origin: { x: 0, y: 0, z: dstCopyBaseArrayLayer }
+ },
+ copySize
+ );
+ t.queue.submit([encoder.finish()]);
+
+ if (kTextureFormatInfo[format].stencil) {
+ assert(initialStencilData !== undefined);
+ t.VerifyStencilAspect(
+ destinationTexture,
+ initialStencilData,
+ dstCopyLevel,
+ dstCopyBaseArrayLayer,
+ copySize
+ );
+ }
+ if (kTextureFormatInfo[format].depth) {
+ t.VerifyDepthAspect(
+ destinationTexture,
+ format,
+ dstCopyLevel,
+ dstCopyBaseArrayLayer,
+ copySize
+ );
+ }
+});
+
+g.test('copy_multisampled_color').
+desc(
+ `
+ Validate the correctness of copyTextureToTexture() with multisampled color formats.
+
+ - Initialize the source texture with a triangle in a render pass.
+ - Copy from the source texture into the destination texture with CopyTextureToTexture().
+ - Compare every sub-pixel of source texture and destination texture in another render pass:
+ - If they are different, then output RED; otherwise output GREEN
+ - Verify the pixels in the output texture are all GREEN.
+ - Note that in current WebGPU SPEC the mipmap level count and array layer count of a multisampled
+ texture can only be 1.
+ `
+).
+fn((t) => {
+ const textureSize = [32, 16, 1];
+ const kColorFormat = 'rgba8unorm';
+ const kSampleCount = 4;
+
+ const sourceTexture = t.device.createTexture({
+ format: kColorFormat,
+ size: textureSize,
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: kSampleCount
+ });
+ t.trackForCleanup(sourceTexture);
+ const destinationTexture = t.device.createTexture({
+ format: kColorFormat,
+ size: textureSize,
+ usage:
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: kSampleCount
+ });
+ t.trackForCleanup(destinationTexture);
+
+ // Initialize sourceTexture with a draw call.
+ const renderPipelineForInit = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>( 1.0, 1.0),
+ vec2<f32>( 1.0, -1.0)
+ );
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.3, 0.5, 0.8, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: kColorFormat }]
+ },
+ multisample: {
+ count: kSampleCount
+ }
+ });
+ const initEncoder = t.device.createCommandEncoder();
+ const renderPassForInit = initEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: sourceTexture.createView(),
+ clearValue: [1.0, 0.0, 0.0, 1.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPassForInit.setPipeline(renderPipelineForInit);
+ renderPassForInit.draw(3);
+ renderPassForInit.end();
+ t.queue.submit([initEncoder.finish()]);
+
+ // Do the texture-to-texture copy
+ const copyEncoder = t.device.createCommandEncoder();
+ copyEncoder.copyTextureToTexture(
+ {
+ texture: sourceTexture
+ },
+ {
+ texture: destinationTexture
+ },
+ textureSize
+ );
+ t.queue.submit([copyEncoder.finish()]);
+
+ // Verify if all the sub-pixel values at the same location of sourceTexture and
+ // destinationTexture are equal.
+ const renderPipelineForValidation = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ 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));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var sourceTexture : texture_multisampled_2d<f32>;
+ @group(0) @binding(1) var destinationTexture : texture_multisampled_2d<f32>;
+ @fragment
+ fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+ var coord_in_vec2 = vec2<i32>(i32(coord_in.x), i32(coord_in.y));
+ for (var sampleIndex = 0; sampleIndex < ${kSampleCount};
+ sampleIndex = sampleIndex + 1) {
+ var sourceSubPixel : vec4<f32> =
+ textureLoad(sourceTexture, coord_in_vec2, sampleIndex);
+ var destinationSubPixel : vec4<f32> =
+ textureLoad(destinationTexture, coord_in_vec2, sampleIndex);
+ if (!all(sourceSubPixel == destinationSubPixel)) {
+ return vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }
+ }
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: kColorFormat }]
+ }
+ });
+ const bindGroup = t.device.createBindGroup({
+ layout: renderPipelineForValidation.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: sourceTexture.createView()
+ },
+ {
+ binding: 1,
+ resource: destinationTexture.createView()
+ }]
+
+ });
+ const expectedOutputTexture = t.device.createTexture({
+ format: kColorFormat,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.trackForCleanup(expectedOutputTexture);
+ const validationEncoder = t.device.createCommandEncoder();
+ const renderPassForValidation = validationEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: expectedOutputTexture.createView(),
+ clearValue: [1.0, 0.0, 0.0, 1.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPassForValidation.setPipeline(renderPipelineForValidation);
+ renderPassForValidation.setBindGroup(0, bindGroup);
+ renderPassForValidation.draw(6);
+ renderPassForValidation.end();
+ t.queue.submit([validationEncoder.finish()]);
+
+ t.expectSingleColor(expectedOutputTexture, 'rgba8unorm', {
+ size: [textureSize[0], textureSize[1], textureSize[2]],
+ exp: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 }
+ });
+});
+
+g.test('copy_multisampled_depth').
+desc(
+ `
+ Validate the correctness of copyTextureToTexture() with multisampled depth formats.
+
+ - Initialize the source texture with a triangle in a render pass.
+ - Copy from the source texture into the destination texture with CopyTextureToTexture().
+ - Validate the content in the destination texture with the depth comparison function 'equal'.
+ - Note that in current WebGPU SPEC the mipmap level count and array layer count of a multisampled
+ texture can only be 1.
+ `
+).
+fn((t) => {
+ const textureSize = [32, 16, 1];
+ const kDepthFormat = 'depth24plus';
+ const kSampleCount = 4;
+
+ const sourceTexture = t.device.createTexture({
+ format: kDepthFormat,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: kSampleCount
+ });
+ t.trackForCleanup(sourceTexture);
+ const destinationTexture = t.device.createTexture({
+ format: kDepthFormat,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: kSampleCount
+ });
+ t.trackForCleanup(destinationTexture);
+
+ const vertexState = {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
+ var pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
+ vec3<f32>(-1.0, 1.0, 0.5),
+ vec3<f32>(-1.0, -1.0, 0.0),
+ vec3<f32>( 1.0, 1.0, 1.0),
+ vec3<f32>(-1.0, -1.0, 0.0),
+ vec3<f32>( 1.0, 1.0, 1.0),
+ vec3<f32>( 1.0, -1.0, 0.5));
+ return vec4<f32>(pos[VertexIndex], 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ };
+
+ // Initialize the depth aspect of source texture with a draw call
+ const renderPipelineForInit = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: vertexState,
+ depthStencil: {
+ format: kDepthFormat,
+ depthCompare: 'always',
+ depthWriteEnabled: true
+ },
+ multisample: {
+ count: kSampleCount
+ }
+ });
+
+ const encoderForInit = t.device.createCommandEncoder();
+ const renderPassForInit = encoderForInit.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: sourceTexture.createView(),
+ depthClearValue: 0.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store'
+ }
+ });
+ renderPassForInit.setPipeline(renderPipelineForInit);
+ renderPassForInit.draw(6);
+ renderPassForInit.end();
+ t.queue.submit([encoderForInit.finish()]);
+
+ // Do the texture-to-texture copy
+ const copyEncoder = t.device.createCommandEncoder();
+ copyEncoder.copyTextureToTexture(
+ {
+ texture: sourceTexture
+ },
+ {
+ texture: destinationTexture
+ },
+ textureSize
+ );
+ t.queue.submit([copyEncoder.finish()]);
+
+ // Verify the depth values in destinationTexture are what we expected with
+ // depthCompareFunction == 'equal' and depthWriteEnabled == false in the render pipeline
+ const kColorFormat = 'rgba8unorm';
+ const renderPipelineForVerify = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: vertexState,
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: kColorFormat }]
+ },
+ depthStencil: {
+ format: kDepthFormat,
+ depthCompare: 'equal',
+ depthWriteEnabled: false
+ },
+ multisample: {
+ count: kSampleCount
+ }
+ });
+ const multisampledColorTexture = t.device.createTexture({
+ format: kColorFormat,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: kSampleCount
+ });
+ t.trackForCleanup(multisampledColorTexture);
+ const colorTextureAsResolveTarget = t.device.createTexture({
+ format: kColorFormat,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.trackForCleanup(colorTextureAsResolveTarget);
+
+ const encoderForVerify = t.device.createCommandEncoder();
+ const renderPassForVerify = encoderForVerify.beginRenderPass({
+ colorAttachments: [
+ {
+ view: multisampledColorTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'discard',
+ resolveTarget: colorTextureAsResolveTarget.createView()
+ }],
+
+ depthStencilAttachment: {
+ view: destinationTexture.createView(),
+ depthLoadOp: 'load',
+ depthStoreOp: 'store'
+ }
+ });
+ renderPassForVerify.setPipeline(renderPipelineForVerify);
+ renderPassForVerify.draw(6);
+ renderPassForVerify.end();
+ t.queue.submit([encoderForVerify.finish()]);
+
+ t.expectSingleColor(colorTextureAsResolveTarget, kColorFormat, {
+ size: [textureSize[0], textureSize[1], textureSize[2]],
+ exp: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/image_copy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/image_copy.spec.js
new file mode 100644
index 0000000000..a86ebec917
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/image_copy.spec.js
@@ -0,0 +1,2098 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `writeTexture + copyBufferToTexture + copyTextureToBuffer operation tests.
+
+* copy_with_various_rows_per_image_and_bytes_per_row: test that copying data with various bytesPerRow (including { ==, > } bytesInACompleteRow) and\
+ rowsPerImage (including { ==, > } copyExtent.height) values and minimum required bytes in copy works for every format. Also covers special code paths:
+ - bufferSize - offset < bytesPerImage * copyExtent.depthOrArrayLayers
+ - when bytesPerRow is not a multiple of 512 and copyExtent.depthOrArrayLayers > 1: copyExtent.depthOrArrayLayers % 2 == { 0, 1 }
+ - bytesPerRow == bytesInACompleteCopyImage
+
+* copy_with_various_offsets_and_data_sizes: test that copying data with various offset (including { ==, > } 0 and is/isn't power of 2) values and additional\
+ data paddings works for every format with 2d and 2d-array textures. Also covers special code paths:
+ - offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
+ - offset > bytesInACompleteCopyImage
+
+* copy_with_various_origins_and_copy_extents: test that copying slices of a texture works with various origin (including { origin.x, origin.y, origin.z }\
+ { ==, > } 0 and is/isn't power of 2) and copyExtent (including { copyExtent.x, copyExtent.y, copyExtent.z } { ==, > } 0 and is/isn't power of 2) values\
+ (also including {origin._ + copyExtent._ { ==, < } the subresource size of textureCopyView) works for all formats. origin and copyExtent values are passed\
+ as [number, number, number] instead of GPUExtent3DDict.
+
+* copy_various_mip_levels: test that copying various mip levels works for all formats. Also covers special code paths:
+ - the physical size of the subresource is not equal to the logical size
+ - bufferSize - offset < bytesPerImage * copyExtent.depthOrArrayLayers and copyExtent needs to be clamped
+
+* 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.
+
+* 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
+ DoCopyTextureToBufferWithDepthAspectTest().
+
+TODO: Expand tests of GPUExtent3D [1]
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ assert,
+ ErrorWithExtra,
+ memcpy,
+
+ unreachable } from
+'../../../../common/util/util.js';
+import {
+ kMinDynamicBufferOffsetAlignment,
+ kBufferSizeAlignment,
+ kTextureDimensions } from
+'../../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kDepthStencilFormats,
+ kColorTextureFormats,
+ depthStencilBufferTextureCopySupported,
+ textureDimensionAndFormatCompatible,
+ depthStencilFormatAspectSize,
+
+
+
+ isCompressedTextureFormat } from
+'../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { makeBufferWithContents } from '../../../util/buffer.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
+import { align } from '../../../util/math.js';
+import { physicalMipSizeFromTexture } from '../../../util/texture/base.js';
+import { DataArrayGenerator } from '../../../util/texture/data_generation.js';
+import {
+ bytesInACompleteRow,
+ dataBytesForCopyOrFail,
+ getTextureCopyLayout,
+ kBytesPerRowAlignment } from
+
+'../../../util/texture/layout.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../../util/texture/texture_ok.js';
+
+
+
+
+
+
+
+/** Describes the function used to copy the initial data into the texture. */
+
+/**
+ * - 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
+ * the data we were copying and that the nothing else was modified - that's primarily for testing
+ * WriteTexture and CopyB2T.
+ *
+ * Note: in compatibility mode, copyTextureToBuffer is not supported for compressed textures.
+ * In this case, we render the texture as well as a texture with the contents we except in the
+ * copy and then expect the rendered results to match.
+ */
+
+
+/**
+ * This describes in what form the arguments will be passed to WriteTexture/CopyB2T/CopyT2B. If
+ * undefined, then default values are passed as undefined instead of default values. If arrays, then
+ * `GPUOrigin3D` and `GPUExtent3D` are passed as `[number, number, number]`. *
+ *
+ * [1]: Try to expand this with something like:
+ * ```ts
+ * function encodeExtent3D(
+ * mode: 'partial-array' | 'full-array' | 'extra-array' | 'partial-dict' | 'full-dict',
+ * value: GPUExtent3D
+ * ): GPUExtent3D { ... }
+ * ```
+ */
+
+
+/** Each combination of methods assume that the ones before it were tested and work correctly. */
+const kMethodsToTest = [
+// Then we make sure that WriteTexture works for all formats:
+{ initMethod: 'WriteTexture', checkMethod: 'FullCopyT2B' },
+// Then we make sure that CopyB2T works for all formats:
+{ initMethod: 'CopyB2T', checkMethod: 'FullCopyT2B' },
+// Then we make sure that CopyT2B works for all formats:
+{ initMethod: 'WriteTexture', checkMethod: 'PartialCopyT2B' }];
+
+
+const dataGenerator = new DataArrayGenerator();
+const altDataGenerator = new DataArrayGenerator();
+
+class ImageCopyTest extends TextureTestMixin(GPUTest) {
+ /**
+ * This is used for testing passing undefined members of `GPUImageDataLayout` instead of actual
+ * values where possible. Passing arguments as values and not as objects so that they are passed
+ * by copy and not by reference.
+ */
+ undefDataLayoutIfNeeded(
+ offset,
+ rowsPerImage,
+ bytesPerRow,
+ changeBeforePass)
+ {
+ if (changeBeforePass === 'undefined') {
+ if (offset === 0) {
+ offset = undefined;
+ }
+ if (bytesPerRow === 0) {
+ bytesPerRow = undefined;
+ }
+ if (rowsPerImage === 0) {
+ rowsPerImage = undefined;
+ }
+ }
+ return { offset, bytesPerRow, rowsPerImage };
+ }
+
+ /**
+ * This is used for testing passing undefined members of `GPUImageCopyTexture` instead of actual
+ * values where possible and also for testing passing the origin as `[number, number, number]`.
+ * Passing arguments as values and not as objects so that they are passed by copy and not by
+ * reference.
+ */
+ undefOrArrayCopyViewIfNeeded(
+ texture,
+ origin_x,
+ origin_y,
+ origin_z,
+ mipLevel,
+ changeBeforePass)
+ {
+ let origin = { x: origin_x, y: origin_y, z: origin_z };
+
+ if (changeBeforePass === 'undefined') {
+ if (origin_x === 0 && origin_y === 0 && origin_z === 0) {
+ origin = undefined;
+ } else {
+ if (origin_x === 0) {
+ origin_x = undefined;
+ }
+ if (origin_y === 0) {
+ origin_y = undefined;
+ }
+ if (origin_z === 0) {
+ origin_z = undefined;
+ }
+ origin = { x: origin_x, y: origin_y, z: origin_z };
+ }
+
+ if (mipLevel === 0) {
+ mipLevel = undefined;
+ }
+ }
+
+ if (changeBeforePass === 'arrays') {
+ origin = [origin_x, origin_y, origin_z];
+ }
+
+ return { texture, origin, mipLevel };
+ }
+
+ /**
+ * This is used for testing passing `GPUExtent3D` as `[number, number, number]` instead of
+ * `GPUExtent3DDict`. Passing arguments as values and not as objects so that they are passed by
+ * copy and not by reference.
+ */
+ arrayCopySizeIfNeeded(
+ width,
+ height,
+ depthOrArrayLayers,
+ changeBeforePass)
+ {
+ if (changeBeforePass === 'arrays') {
+ return [width, height, depthOrArrayLayers];
+ } else {
+ return { width, height, depthOrArrayLayers };
+ }
+ }
+
+ /**
+ * Compares data in `expected` to data in `buffer.
+ * Areas defined by size and dataLayout are compared by interpreting the data as appropriate
+ * for the texture format. As an example, with 'rgb9e5ufloat' multiple values can
+ * represent the same number. For example, double the exponent and halving the
+ * mantissa. Areas outside the area defined by size and dataLayout are expected to match
+ * by binary comparison.
+ */
+ expectGPUBufferValuesEqualWhenInterpretedAsTextureFormat(
+ expected,
+ buffer,
+ format,
+ size,
+ dataLayout)
+ {
+ if (isCompressedTextureFormat(format)) {
+ this.expectGPUBufferValuesEqual(buffer, expected);
+ return;
+ }
+ const regularFormat = format;
+ // data is in a format like this
+ //
+ // ....
+ // ttttt..
+ // ttttt..
+ // ttttt..
+ // .......
+ // ttttt..
+ // ttttt..
+ // ttttt...
+ //
+ // where the first `....` represents the portion of the buffer before
+ // `dataLayout.offset`. `ttttt` represents width (size[0]) and `..`
+ // represents the portion when `dataLayout.bytesPerRow` is greater than the
+ // data needed for width texels. `......` represents when height (size[1])
+ // is less than `dataLayout.rowsPerImage`. `...` represents any data past
+ // ((height - 1) * depth * bytePerRow + bytesPerRow) and the end of the
+ // buffer
+ const checkByTextureFormat = (actual) => {
+ const zero = { x: 0, y: 0, z: 0 };
+
+ // compare texel areas
+ {
+ const actTexelView = TexelView.fromTextureDataByReference(regularFormat, actual, {
+ bytesPerRow: dataLayout.bytesPerRow,
+ rowsPerImage: dataLayout.rowsPerImage,
+ subrectOrigin: [0, 0, 0],
+ subrectSize: size
+ });
+ const expTexelView = TexelView.fromTextureDataByReference(regularFormat, expected, {
+ bytesPerRow: dataLayout.bytesPerRow,
+ rowsPerImage: dataLayout.rowsPerImage,
+ subrectOrigin: [0, 0, 0],
+ subrectSize: size
+ });
+
+ const failedPixelsMessage = findFailedPixels(
+ regularFormat,
+ zero,
+ size,
+ { actTexelView, expTexelView },
+ {
+ maxFractionalDiff: 0
+ }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
+ return new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView
+ }));
+ }
+ }
+
+ // compare non texel areas
+ {
+ const rowLength = bytesInACompleteRow(size.width, format);
+ let lastOffset = 0;
+ for (const texel of this.iterateBlockRows(size, format)) {
+ const offset = this.getTexelOffsetInBytes(dataLayout, format, texel, zero);
+ const actualPart = actual.subarray(lastOffset, offset);
+ const expectedPart = expected.subarray(lastOffset, offset);
+ const error = checkElementsEqual(actualPart, expectedPart);
+ if (error) {
+ return error;
+ }
+ assert(offset >= lastOffset); // make sure iterateBlockRows always goes forward
+ lastOffset = offset + rowLength;
+ }
+ // compare end of buffers
+ {
+ const actualPart = actual.subarray(lastOffset, actual.length);
+ const expectedPart = expected.subarray(lastOffset, expected.length);
+ return checkElementsEqual(actualPart, expectedPart);
+ }
+ }
+ };
+
+ this.expectGPUBufferValuesPassCheck(buffer, checkByTextureFormat, {
+ srcByteOffset: 0,
+ type: Uint8Array,
+ typedLength: expected.length,
+ method: 'copy',
+ mode: 'fail'
+ });
+ }
+
+ /** Run a CopyT2B command with appropriate arguments corresponding to `ChangeBeforePass` */
+ copyTextureToBufferWithAppliedArguments(
+ buffer,
+ { offset, rowsPerImage, bytesPerRow },
+ { width, height, depthOrArrayLayers },
+ { texture, mipLevel, origin },
+ changeBeforePass)
+ {
+ const { x, y, z } = origin;
+
+ const appliedCopyView = this.undefOrArrayCopyViewIfNeeded(
+ texture,
+ x,
+ y,
+ z,
+ mipLevel,
+ changeBeforePass
+ );
+ const appliedDataLayout = this.undefDataLayoutIfNeeded(
+ offset,
+ rowsPerImage,
+ bytesPerRow,
+ changeBeforePass
+ );
+ const appliedCheckSize = this.arrayCopySizeIfNeeded(
+ width,
+ height,
+ depthOrArrayLayers,
+ changeBeforePass
+ );
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ appliedCopyView,
+ { buffer, ...appliedDataLayout },
+ appliedCheckSize
+ );
+ this.device.queue.submit([encoder.finish()]);
+ }
+
+ /** Put data into a part of the texture with an appropriate method. */
+ uploadLinearTextureDataToTextureSubBox(
+ textureCopyView,
+ textureDataLayout,
+ copySize,
+ partialData,
+ method,
+ changeBeforePass)
+ {
+ const { texture, mipLevel, origin } = textureCopyView;
+ const { offset, rowsPerImage, bytesPerRow } = textureDataLayout;
+ const { x, y, z } = origin;
+ const { width, height, depthOrArrayLayers } = copySize;
+
+ const appliedCopyView = this.undefOrArrayCopyViewIfNeeded(
+ texture,
+ x,
+ y,
+ z,
+ mipLevel,
+ changeBeforePass
+ );
+ const appliedDataLayout = this.undefDataLayoutIfNeeded(
+ offset,
+ rowsPerImage,
+ bytesPerRow,
+ changeBeforePass
+ );
+ const appliedCopySize = this.arrayCopySizeIfNeeded(
+ width,
+ height,
+ depthOrArrayLayers,
+ changeBeforePass
+ );
+
+ switch (method) {
+ case 'WriteTexture':{
+ this.device.queue.writeTexture(
+ appliedCopyView,
+ partialData,
+ appliedDataLayout,
+ appliedCopySize
+ );
+
+ break;
+ }
+ case 'CopyB2T':{
+ const buffer = this.makeBufferWithContents(partialData, GPUBufferUsage.COPY_SRC);
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ { buffer, ...appliedDataLayout },
+ appliedCopyView,
+ appliedCopySize
+ );
+ this.device.queue.submit([encoder.finish()]);
+
+ break;
+ }
+ default:
+ unreachable();
+ }
+ }
+
+ generateMatchingTextureInJSRenderAndCompareContents(
+ {
+ texture: actualTexture,
+ mipLevel: mipLevelOrUndefined,
+ origin
+ },
+ copySize,
+ format,
+ expected,
+ expectedDataLayout)
+ {
+ const size = [
+ actualTexture.width,
+ actualTexture.height,
+ actualTexture.depthOrArrayLayers];
+
+ const expectedTexture = this.device.createTexture({
+ label: 'expectedTexture',
+ size,
+ dimension: actualTexture.dimension,
+ format,
+ mipLevelCount: actualTexture.mipLevelCount,
+ usage: actualTexture.usage
+ });
+ this.trackForCleanup(expectedTexture);
+
+ const mipLevel = mipLevelOrUndefined || 0;
+ const fullMipLevelTextureCopyLayout = getTextureCopyLayout(
+ format,
+ actualTexture.dimension,
+ size,
+ {
+ mipLevel
+ }
+ );
+
+ // allocate data for entire mip level.
+ const expectedTextureMipLevelData = new Uint8Array(
+ align(fullMipLevelTextureCopyLayout.byteLength, 4)
+ );
+ const mipSize = physicalMipSizeFromTexture(expectedTexture, mipLevel);
+
+ // update the data for the entire mip level with the data
+ // that would be copied to the "actual" texture
+ this.updateLinearTextureDataSubBox(format, copySize, {
+ src: {
+ dataLayout: expectedDataLayout,
+ origin: { x: 0, y: 0, z: 0 },
+ data: expected
+ },
+ dest: {
+ dataLayout: { offset: 0, ...fullMipLevelTextureCopyLayout },
+ origin,
+ data: expectedTextureMipLevelData
+ }
+ });
+
+ // MAINTENANCE_TODO: If we're testing writeTexture should this use copyBufferToTexture instead?
+ this.queue.writeTexture(
+ { texture: expectedTexture, mipLevel },
+ expectedTextureMipLevelData,
+ { ...fullMipLevelTextureCopyLayout, offset: 0 },
+ mipSize
+ );
+
+ this.expectTexturesToMatchByRendering(
+ actualTexture,
+ expectedTexture,
+ mipLevel,
+ origin,
+ copySize
+ );
+ }
+
+ /**
+ * We check an appropriate part of the texture against the given data.
+ * Used directly with PartialCopyT2B check method (for a subpart of the texture)
+ * and by `copyWholeTextureToBufferAndCheckContentsWithUpdatedData` with FullCopyT2B check method
+ * (for the whole texture).
+ */
+ copyPartialTextureToBufferAndCheckContents(
+ { texture, mipLevel, origin },
+ checkSize,
+ format,
+ expected,
+ expectedDataLayout,
+ changeBeforePass = 'none')
+ {
+ // The alignment is necessary because we need to copy and map data from this buffer.
+ const bufferSize = align(expected.byteLength, 4);
+ // The start value ensures generated data here doesn't match the expected data.
+ const bufferData = altDataGenerator.generateAndCopyView(bufferSize, 17);
+
+ const buffer = this.makeBufferWithContents(
+ bufferData,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ // At this point both buffer and bufferData have the same random data in
+ // them. We'll use copyTextureToBuffer to update buffer with data from the
+ // texture and updateLinearTextureDataSubBox to update bufferData with the
+ // data we originally uploaded to the texture.
+
+ // buffer has ...... in it.
+ // Copy to buffer the portion of texture that was previously uploaded.
+ // After execution buffer has t.t.t. because the rows are padded.
+ this.copyTextureToBufferWithAppliedArguments(
+ buffer,
+ expectedDataLayout,
+ checkSize,
+ { texture, mipLevel, origin },
+ changeBeforePass
+ );
+
+ // We originally copied expected to texture using expectedDataLayout.
+ // We're copying back out of texture above.
+
+ // bufferData has ...... in it.
+ // Update bufferData to have the same contents as buffer.
+ // When done, bufferData now has t.t.t. because the rows are padded.
+ this.updateLinearTextureDataSubBox(format, checkSize, {
+ src: {
+ dataLayout: expectedDataLayout,
+ origin: { x: 0, y: 0, z: 0 },
+ data: expected
+ },
+ dest: {
+ dataLayout: expectedDataLayout,
+ origin: { x: 0, y: 0, z: 0 },
+ data: bufferData
+ }
+ });
+
+ this.expectGPUBufferValuesEqualWhenInterpretedAsTextureFormat(
+ bufferData,
+ buffer,
+ format,
+ checkSize,
+ expectedDataLayout
+ );
+ }
+
+ /**
+ * Used for checking whether the whole texture was updated correctly by
+ * `uploadLinearTextureDataToTextureSubpart`. Takes fullData returned by
+ * `copyWholeTextureToNewBuffer` before the copy operation which is the original texture data,
+ * then updates it with `updateLinearTextureDataSubpart` and checks the texture against the
+ * updated data after the copy operation.
+ */
+ copyWholeTextureToBufferAndCheckContentsWithUpdatedData(
+ { texture, mipLevel, origin },
+ fullTextureCopyLayout,
+ texturePartialDataLayout,
+ copySize,
+ format,
+ fullData,
+ partialData)
+ {
+ const { mipSize, bytesPerRow, rowsPerImage, byteLength } = fullTextureCopyLayout;
+ const readbackPromise = this.readGPUBufferRangeTyped(fullData, {
+ type: Uint8Array,
+ typedLength: byteLength
+ });
+
+ const destinationOrigin = { x: 0, y: 0, z: 0 };
+
+ // We add an eventual async expectation which will update the full data and then add
+ // other eventual async expectations to ensure it will be correct.
+ this.eventualAsyncExpectation(async () => {
+ const readback = await readbackPromise;
+ this.updateLinearTextureDataSubBox(format, copySize, {
+ dest: {
+ dataLayout: { offset: 0, ...fullTextureCopyLayout },
+ origin,
+ data: readback.data
+ },
+ src: {
+ dataLayout: texturePartialDataLayout,
+ origin: { x: 0, y: 0, z: 0 },
+ data: partialData
+ }
+ });
+ this.copyPartialTextureToBufferAndCheckContents(
+ { texture, mipLevel, origin: destinationOrigin },
+ { width: mipSize[0], height: mipSize[1], depthOrArrayLayers: mipSize[2] },
+ format,
+ readback.data,
+ { bytesPerRow, rowsPerImage, offset: 0 }
+ );
+ readback.cleanup();
+ });
+ }
+
+ /**
+ * Tests copy between linear data and texture by creating a texture, putting some data into it
+ * with WriteTexture/CopyB2T, then getting data for the whole texture/for a part of it back and
+ * comparing it with the expectation.
+ */
+ uploadTextureAndVerifyCopy({
+ textureDataLayout,
+ copySize,
+ dataSize,
+ mipLevel = 0,
+ origin = { x: 0, y: 0, z: 0 },
+ textureSize,
+ format,
+ dimension,
+ initMethod,
+ checkMethod,
+ changeBeforePass = 'none'
+
+
+
+
+
+
+
+
+
+
+
+
+ }) {
+ const texture = this.device.createTexture({
+ size: textureSize,
+ format,
+ dimension,
+ mipLevelCount: mipLevel + 1,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
+ });
+ this.trackForCleanup(texture);
+
+ const data = dataGenerator.generateView(dataSize);
+
+ switch (checkMethod) {
+ case 'PartialCopyT2B':{
+ this.uploadLinearTextureDataToTextureSubBox(
+ { texture, mipLevel, origin },
+ textureDataLayout,
+ copySize,
+ data,
+ initMethod,
+ changeBeforePass
+ );
+
+ if (this.canCallCopyTextureToBufferWithTextureFormat(texture.format)) {
+ this.copyPartialTextureToBufferAndCheckContents(
+ { texture, mipLevel, origin },
+ copySize,
+ format,
+ data,
+ textureDataLayout,
+ changeBeforePass
+ );
+ } else {
+ this.generateMatchingTextureInJSRenderAndCompareContents(
+ { texture, mipLevel, origin },
+ copySize,
+ format,
+ data,
+ textureDataLayout
+ );
+ }
+ break;
+ }
+ case 'FullCopyT2B':{
+ this.uploadLinearTextureDataToTextureSubBox(
+ { texture, mipLevel, origin },
+ textureDataLayout,
+ copySize,
+ data,
+ initMethod,
+ changeBeforePass
+ );
+
+ if (this.canCallCopyTextureToBufferWithTextureFormat(texture.format)) {
+ const fullTextureCopyLayout = getTextureCopyLayout(format, dimension, textureSize, {
+ mipLevel
+ });
+
+ const fullData = this.copyWholeTextureToNewBuffer(
+ { texture, mipLevel },
+ fullTextureCopyLayout
+ );
+
+ this.copyWholeTextureToBufferAndCheckContentsWithUpdatedData(
+ { texture, mipLevel, origin },
+ fullTextureCopyLayout,
+ textureDataLayout,
+ copySize,
+ format,
+ fullData,
+ data
+ );
+ } else {
+ this.generateMatchingTextureInJSRenderAndCompareContents(
+ { texture, mipLevel, origin },
+ copySize,
+ format,
+ data,
+ textureDataLayout
+ //fullTextureCopyLayout,
+ //fullData,
+ );
+ }
+ break;
+ }
+ default:
+ unreachable();
+ }
+ }
+
+ DoUploadToStencilTest(
+ format,
+ textureSize,
+ uploadMethod,
+ bytesPerRow,
+ rowsPerImage,
+ initialDataSize,
+ initialDataOffset,
+ mipLevel)
+ {
+ const srcTexture = this.device.createTexture({
+ size: textureSize,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ format,
+ mipLevelCount: mipLevel + 1
+ });
+ this.trackForCleanup(srcTexture);
+
+ const copySize = [textureSize[0] >> mipLevel, textureSize[1] >> mipLevel, textureSize[2]];
+ const initialData = dataGenerator.generateView(
+ align(initialDataSize, kBufferSizeAlignment),
+ 0,
+ initialDataOffset
+ );
+ switch (uploadMethod) {
+ case 'WriteTexture':
+ this.queue.writeTexture(
+ { texture: srcTexture, aspect: 'stencil-only', mipLevel },
+ initialData,
+ {
+ offset: initialDataOffset,
+ bytesPerRow,
+ rowsPerImage
+ },
+ copySize
+ );
+ break;
+ case 'CopyB2T':
+ {
+ const stagingBuffer = makeBufferWithContents(
+ this.device,
+ initialData,
+ GPUBufferUsage.COPY_SRC
+ );
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ { buffer: stagingBuffer, offset: initialDataOffset, bytesPerRow, rowsPerImage },
+ { texture: srcTexture, aspect: 'stencil-only', mipLevel },
+ copySize
+ );
+ this.queue.submit([encoder.finish()]);
+ }
+ break;
+ default:
+ unreachable();
+ }
+
+ this.checkStencilTextureContent(
+ srcTexture,
+ textureSize,
+ format,
+ initialData,
+ initialDataOffset,
+ bytesPerRow,
+ rowsPerImage,
+ mipLevel
+ );
+ }
+
+ DoCopyFromStencilTest(
+ format,
+ textureSize,
+ bytesPerRow,
+ rowsPerImage,
+ offset,
+ mipLevel)
+ {
+ const srcTexture = this.device.createTexture({
+ size: textureSize,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ format,
+ mipLevelCount: mipLevel + 1
+ });
+ this.trackForCleanup(srcTexture);
+
+ // Initialize srcTexture with queue.writeTexture()
+ const copySize = [textureSize[0] >> mipLevel, textureSize[1] >> mipLevel, textureSize[2]];
+ const initialData = dataGenerator.generateView(
+ align(copySize[0] * copySize[1] * copySize[2], kBufferSizeAlignment)
+ );
+ this.queue.writeTexture(
+ { texture: srcTexture, aspect: 'stencil-only', mipLevel },
+ initialData,
+ { bytesPerRow: copySize[0], rowsPerImage: copySize[1] },
+ copySize
+ );
+
+ // Copy the stencil aspect from srcTexture into outputBuffer.
+ const outputBufferSize = align(
+ offset +
+ dataBytesForCopyOrFail({
+ layout: { bytesPerRow, rowsPerImage },
+ format: 'stencil8',
+ copySize,
+ method: 'CopyT2B'
+ }),
+ kBufferSizeAlignment
+ );
+ const outputBuffer = this.device.createBuffer({
+ size: outputBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(outputBuffer);
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ { texture: srcTexture, aspect: 'stencil-only', mipLevel },
+ { buffer: outputBuffer, offset, bytesPerRow, rowsPerImage },
+ copySize
+ );
+ this.queue.submit([encoder.finish()]);
+
+ // Validate the data in outputBuffer is what we expect.
+ const expectedData = new Uint8Array(outputBufferSize);
+ for (let z = 0; z < copySize[2]; ++z) {
+ const baseExpectedOffset = offset + z * bytesPerRow * rowsPerImage;
+ const baseInitialDataOffset = z * copySize[0] * copySize[1];
+ for (let y = 0; y < copySize[1]; ++y) {
+ memcpy(
+ {
+ src: initialData,
+ start: baseInitialDataOffset + y * copySize[0],
+ length: copySize[0]
+ },
+ { dst: expectedData, start: baseExpectedOffset + y * bytesPerRow }
+ );
+ }
+ }
+ this.expectGPUBufferValuesEqual(outputBuffer, expectedData);
+ }
+
+ // MAINTENANCE_TODO(#881): Migrate this into the texture_ok helpers.
+ checkStencilTextureContent(
+ stencilTexture,
+ stencilTextureSize,
+ stencilTextureFormat,
+ expectedStencilTextureData,
+ expectedStencilTextureDataOffset,
+ expectedStencilTextureDataBytesPerRow,
+ expectedStencilTextureDataRowsPerImage,
+ stencilTextureMipLevel)
+ {
+ const stencilBitCount = 8;
+
+ // Prepare the uniform buffer that stores the bit indices (from 0 to 7) at stride 256 (required
+ // by Dynamic Buffer Offset).
+ const uniformBufferSize = kMinDynamicBufferOffsetAlignment * (stencilBitCount - 1) + 4;
+ const uniformBufferData = new Uint32Array(uniformBufferSize / 4);
+ for (let i = 1; i < stencilBitCount; ++i) {
+ uniformBufferData[kMinDynamicBufferOffsetAlignment / 4 * i] = i;
+ }
+ const uniformBuffer = makeBufferWithContents(
+ this.device,
+ uniformBufferData,
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM
+ );
+
+ // Prepare the base render pipeline descriptor (all the settings expect stencilReadMask).
+ const bindGroupLayout = this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ buffer: {
+ type: 'uniform',
+ minBindingSize: 4,
+ hasDynamicOffset: true
+ }
+ }]
+
+ });
+ const renderPipelineDescriptorBase = {
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 6> = 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));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ struct Params {
+ stencilBitIndex: u32
+ };
+ @group(0) @binding(0) var<uniform> param: Params;
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(f32(1u << param.stencilBitIndex) / 255.0, 0.0, 0.0, 0.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [
+ {
+ // As we implement "rendering one bit in each draw() call" with blending operation
+ // 'add', the format of outputTexture must support blending.
+ format: 'r8unorm',
+ blend: {
+ color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
+ alpha: {}
+ }
+ }]
+
+ },
+
+ primitive: {
+ topology: 'triangle-list'
+ },
+
+ depthStencil: {
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ format: stencilTextureFormat,
+ stencilFront: {
+ compare: 'equal'
+ },
+ stencilBack: {
+ compare: 'equal'
+ }
+ }
+ };
+
+ // Prepare the bindGroup that contains uniformBuffer and referenceTexture.
+ const bindGroup = this.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: uniformBuffer,
+ size: 4
+ }
+ }]
+
+ });
+
+ // "Copy" the stencil value into the color attachment with 8 draws in one render pass. Each draw
+ // will "Copy" one bit of the stencil value into the color attachment. The bit of the stencil
+ // value is specified by setStencilReference().
+ const copyFromOutputTextureLayout = getTextureCopyLayout(
+ stencilTextureFormat,
+ '2d',
+ [stencilTextureSize[0], stencilTextureSize[1], 1],
+ {
+ mipLevel: stencilTextureMipLevel,
+ aspect: 'stencil-only'
+ }
+ );
+ const outputTextureSize = [
+ copyFromOutputTextureLayout.mipSize[0],
+ copyFromOutputTextureLayout.mipSize[1],
+ 1];
+
+ const outputTexture = this.device.createTexture({
+ format: 'r8unorm',
+ size: outputTextureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ this.trackForCleanup(outputTexture);
+
+ for (
+ let stencilTextureLayer = 0;
+ stencilTextureLayer < stencilTextureSize[2];
+ ++stencilTextureLayer)
+ {
+ const encoder = this.device.createCommandEncoder();
+ const depthStencilAttachment = {
+ view: stencilTexture.createView({
+ baseMipLevel: stencilTextureMipLevel,
+ mipLevelCount: 1,
+ baseArrayLayer: stencilTextureLayer,
+ arrayLayerCount: 1
+ })
+ };
+ if (kTextureFormatInfo[stencilTextureFormat].depth) {
+ depthStencilAttachment.depthClearValue = 0;
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'store';
+ }
+ if (kTextureFormatInfo[stencilTextureFormat].stencil) {
+ depthStencilAttachment.stencilLoadOp = 'load';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ }
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment
+ });
+
+ for (let stencilBitIndex = 0; stencilBitIndex < stencilBitCount; ++stencilBitIndex) {
+ const renderPipelineDescriptor = renderPipelineDescriptorBase;
+ assert(renderPipelineDescriptor.depthStencil !== undefined);
+ renderPipelineDescriptor.depthStencil.stencilReadMask = 1 << stencilBitIndex;
+ const renderPipeline = this.device.createRenderPipeline(renderPipelineDescriptor);
+
+ renderPass.setPipeline(renderPipeline);
+ renderPass.setStencilReference(1 << stencilBitIndex);
+ renderPass.setBindGroup(0, bindGroup, [stencilBitIndex * kMinDynamicBufferOffsetAlignment]);
+ renderPass.draw(6);
+ }
+ renderPass.end();
+
+ // Check outputTexture by copying the content of outputTexture into outputStagingBuffer and
+ // checking all the data in outputStagingBuffer.
+ const outputStagingBuffer = this.device.createBuffer({
+ size: copyFromOutputTextureLayout.byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(outputStagingBuffer);
+ encoder.copyTextureToBuffer(
+ {
+ texture: outputTexture
+ },
+ {
+ buffer: outputStagingBuffer,
+ bytesPerRow: copyFromOutputTextureLayout.bytesPerRow,
+ rowsPerImage: copyFromOutputTextureLayout.rowsPerImage
+ },
+ outputTextureSize
+ );
+
+ this.queue.submit([encoder.finish()]);
+
+ // Check the valid data in outputStagingBuffer once per row.
+ for (let y = 0; y < copyFromOutputTextureLayout.mipSize[1]; ++y) {
+ const dataStart =
+ expectedStencilTextureDataOffset +
+ expectedStencilTextureDataBytesPerRow *
+ expectedStencilTextureDataRowsPerImage *
+ stencilTextureLayer +
+ expectedStencilTextureDataBytesPerRow * y;
+ this.expectGPUBufferValuesEqual(
+ outputStagingBuffer,
+ expectedStencilTextureData.slice(
+ dataStart,
+ dataStart + copyFromOutputTextureLayout.mipSize[0]
+ ),
+ copyFromOutputTextureLayout.bytesPerRow * y
+ );
+ }
+ }
+ }
+
+ // MAINTENANCE_TODO(#881): Consider if this can be simplified/encapsulated using TexelView.
+ initializeDepthAspectWithRendering(
+ depthTexture,
+ depthFormat,
+ copySize,
+ copyMipLevel,
+ initialData)
+ {
+ assert(!!kTextureFormatInfo[depthFormat].depth);
+
+ const inputTexture = this.device.createTexture({
+ size: copySize,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
+ format: 'r32float'
+ });
+ this.trackForCleanup(inputTexture);
+ this.queue.writeTexture(
+ { texture: inputTexture },
+ initialData,
+ {
+ bytesPerRow: copySize[0] * 4,
+ rowsPerImage: copySize[1]
+ },
+ copySize
+ );
+
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 6> = 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));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var inputTexture: texture_2d<f32>;
+ @fragment fn main(@builtin(position) fragcoord : vec4<f32>) ->
+ @builtin(frag_depth) f32 {
+ var depthValue : vec4<f32> = textureLoad(inputTexture, vec2<i32>(fragcoord.xy), 0);
+ return depthValue.x;
+ }`
+ }),
+ entryPoint: 'main',
+ targets: []
+ },
+ primitive: {
+ topology: 'triangle-list'
+ },
+ depthStencil: {
+ format: depthFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'always'
+ }
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ for (let z = 0; z < copySize[2]; ++z) {
+ const depthStencilAttachment = {
+ view: depthTexture.createView({
+ dimension: '2d',
+ baseArrayLayer: z,
+ arrayLayerCount: 1,
+ baseMipLevel: copyMipLevel,
+ mipLevelCount: 1
+ })
+ };
+ if (kTextureFormatInfo[depthFormat].depth) {
+ depthStencilAttachment.depthClearValue = 0.0;
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'store';
+ }
+ if (kTextureFormatInfo[depthFormat].stencil) {
+ depthStencilAttachment.stencilLoadOp = 'load';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ }
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment
+ });
+ renderPass.setPipeline(renderPipeline);
+
+ const bindGroup = this.device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: inputTexture.createView({
+ dimension: '2d',
+ baseArrayLayer: z,
+ arrayLayerCount: 1,
+ baseMipLevel: 0,
+ mipLevelCount: 1
+ })
+ }]
+
+ });
+ renderPass.setBindGroup(0, bindGroup);
+ renderPass.draw(6);
+ renderPass.end();
+ }
+
+ this.queue.submit([encoder.finish()]);
+ }
+
+ DoCopyTextureToBufferWithDepthAspectTest(
+ format,
+ copySize,
+ bytesPerRowPadding,
+ rowsPerImagePadding,
+ offset,
+ dataPaddingInBytes,
+ mipLevel)
+ {
+ // [2]: need to convert the float32 values in initialData into the ones compatible
+ // to the depth aspect of depthFormats when depth16unorm is supported by the browsers.
+
+ // Generate the initial depth data uploaded to the texture as float32.
+ const initialData = new Float32Array(copySize[0] * copySize[1] * copySize[2]);
+ for (let i = 0; i < initialData.length; ++i) {
+ const baseValue = 0.05 * i;
+
+ // We expect there are both 1's and 0's in initialData.
+ initialData[i] = i % 40 === 0 ? 1 : baseValue - Math.floor(baseValue);
+ assert(initialData[i] >= 0 && initialData[i] <= 1);
+ }
+
+ // The data uploaded to the texture, using the byte pattern of the format.
+ let formatInitialData = initialData;
+
+ // For unorm depth formats, replace the uploaded depth data with quantized data to avoid
+ // rounding issues when converting from 32float to 16unorm.
+ if (format === 'depth16unorm') {
+ const u16Data = new Uint16Array(initialData.length);
+ for (let i = 0; i < initialData.length; i++) {
+ u16Data[i] = initialData[i] * 65535;
+ initialData[i] = u16Data[i] / 65535.0;
+ }
+ formatInitialData = u16Data;
+ }
+
+ // Initialize the depth aspect of the source texture
+ const depthTexture = this.device.createTexture({
+ format,
+ size: [copySize[0] << mipLevel, copySize[1] << mipLevel, copySize[2]],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ mipLevelCount: mipLevel + 1
+ });
+ this.trackForCleanup(depthTexture);
+ this.initializeDepthAspectWithRendering(depthTexture, format, copySize, mipLevel, initialData);
+
+ // Copy the depth aspect of the texture into the destination buffer.
+ const aspectBytesPerBlock = depthStencilFormatAspectSize(format, 'depth-only');
+ const bytesPerRow =
+ align(aspectBytesPerBlock * copySize[0], kBytesPerRowAlignment) +
+ bytesPerRowPadding * kBytesPerRowAlignment;
+ const rowsPerImage = copySize[1] + rowsPerImagePadding;
+
+ const destinationBufferSize = align(
+ bytesPerRow * rowsPerImage * copySize[2] +
+ bytesPerRow * (copySize[1] - 1) +
+ aspectBytesPerBlock * copySize[0] +
+ offset +
+ dataPaddingInBytes,
+ kBufferSizeAlignment
+ );
+ const destinationBuffer = this.device.createBuffer({
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ size: destinationBufferSize
+ });
+ this.trackForCleanup(destinationBuffer);
+ const copyEncoder = this.device.createCommandEncoder();
+ copyEncoder.copyTextureToBuffer(
+ {
+ texture: depthTexture,
+ mipLevel,
+ aspect: 'depth-only'
+ },
+ {
+ buffer: destinationBuffer,
+ offset,
+ bytesPerRow,
+ rowsPerImage
+ },
+ copySize
+ );
+ this.queue.submit([copyEncoder.finish()]);
+
+ // Validate the data in destinationBuffer is what we expect.
+ const expectedData = new Uint8Array(destinationBufferSize);
+ for (let z = 0; z < copySize[2]; ++z) {
+ const baseExpectedOffset = z * bytesPerRow * rowsPerImage + offset;
+ const baseInitialDataOffset = z * copySize[0] * copySize[1];
+ for (let y = 0; y < copySize[1]; ++y) {
+ memcpy(
+ {
+ src: formatInitialData,
+ start: baseInitialDataOffset + y * copySize[0],
+ length: copySize[0]
+ },
+ { dst: expectedData, start: baseExpectedOffset + y * bytesPerRow }
+ );
+ }
+ }
+ this.expectGPUBufferValuesEqual(destinationBuffer, expectedData);
+ }
+}
+
+/**
+ * This is a helper function used for filtering test parameters
+ *
+ * [3]: Modify this after introducing tests with rendering.
+ */
+function formatCanBeTested({ format }) {
+ return kTextureFormatInfo[format].color.copyDst && kTextureFormatInfo[format].color.copySrc;
+}
+
+export const g = makeTestGroup(ImageCopyTest);
+
+const kRowsPerImageAndBytesPerRowParams = {
+ paddings: [
+ { bytesPerRowPadding: 0, rowsPerImagePadding: 0 }, // no padding
+ { bytesPerRowPadding: 0, rowsPerImagePadding: 6 }, // rowsPerImage padding
+ { bytesPerRowPadding: 6, rowsPerImagePadding: 0 }, // bytesPerRow padding
+ { bytesPerRowPadding: 15, rowsPerImagePadding: 17 } // both paddings
+ ],
+
+ copySizes: [
+ // In the two cases below, for (WriteTexture, PartialCopyB2T) and (CopyB2T, FullCopyT2B)
+ // sets of methods we will have bytesPerRow = 256 and copyDepth % 2 == { 0, 1 }
+ // respectively. This covers a special code path for D3D12.
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 5 }, // standard copy
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 2 }, // standard copy
+
+ { copyWidthInBlocks: 0, copyHeightInBlocks: 4, copyDepth: 5 }, // empty copy because of width
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 5 }, // empty copy because of height
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 0 }, // empty copy because of depthOrArrayLayers
+ { copyWidthInBlocks: 256, copyHeightInBlocks: 3, copyDepth: 2 }, // copyWidth is 256-aligned
+ { copyWidthInBlocks: 1, copyHeightInBlocks: 3, copyDepth: 5 }, // copyWidth = 1
+
+ // The two cases below cover another special code path for D3D12.
+ // - For (WriteTexture, FullCopyT2B) with r8unorm:
+ // bytesPerRow = 15 = 3 * 5 = bytesInACompleteCopyImage.
+ { copyWidthInBlocks: 32, copyHeightInBlocks: 1, copyDepth: 8 }, // copyHeight = 1
+ // - For (CopyB2T, FullCopyT2B) and (WriteTexture, PartialCopyT2B) with r8unorm:
+ // bytesPerRow = 256 = 8 * 32 = bytesInACompleteCopyImage.
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 1 }, // copyDepth = 1
+
+ { copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1 } // copyHeight = 1 and copyDepth = 1
+ ],
+
+ // Copy sizes that are suitable for 1D texture and check both some copy sizes and empty copies.
+ copySizes1D: [
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 1, copyDepth: 1 },
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 1, copyDepth: 1 },
+
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 1 },
+ { copyWidthInBlocks: 0, copyHeightInBlocks: 1, copyDepth: 1 },
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 1, copyDepth: 0 }]
+
+};
+
+g.test('rowsPerImage_and_bytesPerRow').
+desc(
+ `Test that copying data with various bytesPerRow and rowsPerImage values and minimum required
+bytes in copy works for every format.
+
+ Covers a special code path for Metal:
+ bufferSize - offset < bytesPerImage * copyExtent.depthOrArrayLayers
+ Covers a special code path for D3D12:
+ when bytesPerRow is not a multiple of 512 and copyExtent.depthOrArrayLayers > 1: copyExtent.depthOrArrayLayers % 2 == { 0, 1 }
+ bytesPerRow == bytesInACompleteCopyImage
+
+ TODO: Cover the special code paths for 3D textures in D3D12.
+ `
+).
+params((u) =>
+u.
+combineWithParams(kMethodsToTest).
+combine('format', kColorTextureFormats).
+filter(formatCanBeTested).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combineWithParams(kRowsPerImageAndBytesPerRowParams.paddings).
+expandWithParams((p) => {
+ if (p.dimension === '1d') {
+ return kRowsPerImageAndBytesPerRowParams.copySizes1D;
+ }
+ return kRowsPerImageAndBytesPerRowParams.copySizes;
+})
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ bytesPerRowPadding,
+ rowsPerImagePadding,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+ // For CopyB2T and CopyT2B we need to have bytesPerRow 256-aligned,
+ // to make this happen we align the bytesInACompleteRow value and multiply
+ // bytesPerRowPadding by 256.
+ const bytesPerRowAlignment =
+ initMethod === 'WriteTexture' && checkMethod === 'FullCopyT2B' ? 1 : 256;
+
+ const copyWidth = copyWidthInBlocks * info.blockWidth;
+ const copyHeight = copyHeightInBlocks * info.blockHeight;
+ const rowsPerImage = copyHeightInBlocks + rowsPerImagePadding;
+ const bytesPerRow =
+ align(bytesInACompleteRow(copyWidth, format), bytesPerRowAlignment) +
+ bytesPerRowPadding * bytesPerRowAlignment;
+ const copySize = { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth };
+
+ const dataSize = dataBytesForCopyOrFail({
+ layout: { offset: 0, bytesPerRow, rowsPerImage },
+ format,
+ copySize,
+ method: initMethod
+ });
+
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ textureSize: [
+ Math.max(copyWidth, info.blockWidth),
+ Math.max(copyHeight, info.blockHeight),
+ Math.max(copyDepth, 1)]
+ /* making sure the texture is non-empty */,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ });
+});
+
+const kOffsetsAndSizesParams = {
+ offsetsAndPaddings: [
+ { offsetInBlocks: 0, dataPaddingInBytes: 0 }, // no offset and no padding
+ { offsetInBlocks: 1, dataPaddingInBytes: 0 }, // offset = 1
+ { offsetInBlocks: 2, dataPaddingInBytes: 0 }, // offset = 2
+ { offsetInBlocks: 15, dataPaddingInBytes: 0 }, // offset = 15
+ { offsetInBlocks: 16, dataPaddingInBytes: 0 }, // offset = 16
+ { offsetInBlocks: 242, dataPaddingInBytes: 0 }, // for rgba8unorm format: offset + bytesInCopyExtentPerRow = 242 + 12 = 256 = bytesPerRow
+ { offsetInBlocks: 243, dataPaddingInBytes: 0 }, // for rgba8unorm format: offset + bytesInCopyExtentPerRow = 243 + 12 > 256 = bytesPerRow
+ { offsetInBlocks: 768, dataPaddingInBytes: 0 }, // for copyDepth = 1, blockWidth = 1 and bytesPerBlock = 1: offset = 768 = 3 * 256 = bytesInACompleteCopyImage
+ { offsetInBlocks: 769, dataPaddingInBytes: 0 }, // for copyDepth = 1, blockWidth = 1 and bytesPerBlock = 1: offset = 769 > 768 = bytesInACompleteCopyImage
+ { offsetInBlocks: 0, dataPaddingInBytes: 1 }, // dataPaddingInBytes > 0
+ { offsetInBlocks: 1, dataPaddingInBytes: 8 } // offset > 0 and dataPaddingInBytes > 0
+ ],
+ copyDepth: [1, 2]
+};
+
+g.test('offsets_and_sizes').
+desc(
+ `Test that copying data with various offset values and additional data paddings
+works for every format with 2d and 2d-array textures.
+
+ Covers two special code paths for D3D12:
+ offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
+ offset > bytesInACompleteCopyImage
+
+ TODO: Cover the special code paths for 3D textures in D3D12.
+ TODO: Make a variant for depth-stencil formats.
+`
+).
+params((u) =>
+u.
+combineWithParams(kMethodsToTest).
+combine('format', kColorTextureFormats).
+filter(formatCanBeTested).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combineWithParams(kOffsetsAndSizesParams.offsetsAndPaddings).
+combine('copyDepth', kOffsetsAndSizesParams.copyDepth) // 2d and 2d-array textures
+.unless((p) => p.dimension === '1d' && p.copyDepth !== 1)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ offsetInBlocks,
+ dataPaddingInBytes,
+ copyDepth,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const offset = offsetInBlocks * info.color.bytes;
+ const copySize = {
+ width: 3 * info.blockWidth,
+ height: 3 * info.blockHeight,
+ depthOrArrayLayers: copyDepth
+ };
+ let textureHeight = 4 * info.blockHeight;
+ let rowsPerImage = 3;
+ const bytesPerRow = 256;
+
+ if (dimension === '1d') {
+ copySize.height = 1;
+ textureHeight = info.blockHeight;
+ rowsPerImage = 1;
+ }
+ const textureSize = [4 * info.blockWidth, textureHeight, copyDepth];
+
+ const minDataSize = dataBytesForCopyOrFail({
+ layout: { offset, bytesPerRow, rowsPerImage },
+ format,
+ copySize,
+ method: initMethod
+ });
+ const dataSize = minDataSize + dataPaddingInBytes;
+
+ // We're copying a (3 x 3 x copyDepth) (in texel blocks) part of a (4 x 4 x copyDepth)
+ // (in texel blocks) texture with no origin.
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ textureSize,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ });
+});
+
+g.test('origins_and_extents').
+desc(
+ `Test that copying slices of a texture works with various origin and copyExtent values
+for all formats. We pass origin and copyExtent as [number, number, number].`
+).
+params((u) =>
+u.
+combineWithParams(kMethodsToTest).
+combine('format', kColorTextureFormats).
+filter(formatCanBeTested).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combine('originValueInBlocks', [0, 7, 8]).
+combine('copySizeValueInBlocks', [0, 7, 8]).
+combine('textureSizePaddingValueInBlocks', [0, 7, 8]).
+unless(
+ (p) =>
+ // we can't create an empty texture
+ p.copySizeValueInBlocks + p.originValueInBlocks + p.textureSizePaddingValueInBlocks === 0
+).
+combine('coordinateToTest', [0, 1, 2]).
+unless((p) => p.dimension === '1d' && p.coordinateToTest !== 0)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ originValueInBlocks,
+ copySizeValueInBlocks,
+ textureSizePaddingValueInBlocks,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ let originBlocks = [1, 1, 1];
+ let copySizeBlocks = [2, 2, 2];
+ let texSizeBlocks = [3, 3, 3];
+ if (dimension === '1d') {
+ originBlocks = [1, 0, 0];
+ copySizeBlocks = [2, 1, 1];
+ texSizeBlocks = [3, 1, 1];
+ }
+
+ {
+ const ctt = t.params.coordinateToTest;
+ originBlocks[ctt] = originValueInBlocks;
+ copySizeBlocks[ctt] = copySizeValueInBlocks;
+ texSizeBlocks[ctt] =
+ originBlocks[ctt] + copySizeBlocks[ctt] + textureSizePaddingValueInBlocks;
+ }
+
+ const origin = {
+ x: originBlocks[0] * info.blockWidth,
+ y: originBlocks[1] * info.blockHeight,
+ z: originBlocks[2]
+ };
+ const copySize = {
+ width: copySizeBlocks[0] * info.blockWidth,
+ height: copySizeBlocks[1] * info.blockHeight,
+ depthOrArrayLayers: copySizeBlocks[2]
+ };
+ const textureSize = [
+ texSizeBlocks[0] * info.blockWidth,
+ texSizeBlocks[1] * info.blockHeight,
+ texSizeBlocks[2]];
+
+
+ const rowsPerImage = copySizeBlocks[1];
+ const bytesPerRow = align(copySizeBlocks[0] * info.color.bytes, 256);
+
+ const dataSize = dataBytesForCopyOrFail({
+ layout: { offset: 0, bytesPerRow, rowsPerImage },
+ format,
+ copySize,
+ method: initMethod
+ });
+
+ // For testing width: we copy a (_ x 2 x 2) (in texel blocks) part of a (_ x 3 x 3)
+ // (in texel blocks) texture with origin (_, 1, 1) (in texel blocks).
+ // Similarly for other coordinates.
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ origin,
+ textureSize,
+ format,
+ dimension,
+ initMethod,
+ checkMethod,
+ changeBeforePass: 'arrays'
+ });
+});
+
+/**
+ * Generates textureSizes which correspond to the same physicalSizeAtMipLevel including virtual
+ * sizes at mip level different from the physical ones.
+ */
+function* generateTestTextureSizes({
+ format,
+ dimension,
+ mipLevel,
+ _mipSizeInBlocks
+
+
+
+
+
+}) {
+ assert(dimension !== '1d'); // textureSize[1] would be wrong for 1D mipped textures.
+ const info = kTextureFormatInfo[format];
+
+ const widthAtThisLevel = _mipSizeInBlocks.width * info.blockWidth;
+ const heightAtThisLevel = _mipSizeInBlocks.height * info.blockHeight;
+ const textureSize = [
+ widthAtThisLevel << mipLevel,
+ heightAtThisLevel << mipLevel,
+ _mipSizeInBlocks.depthOrArrayLayers << (dimension === '3d' ? mipLevel : 0)];
+
+ yield textureSize;
+
+ // We choose width and height of the texture so that the values are divisible by blockWidth and
+ // blockHeight respectively and so that the virtual size at mip level corresponds to the same
+ // physical size.
+ // Virtual size at mip level with modified width has width = (physical size width) - (blockWidth / 2).
+ // Virtual size at mip level with modified height has height = (physical size height) - (blockHeight / 2).
+ const widthAtPrevLevel = widthAtThisLevel << 1;
+ const heightAtPrevLevel = heightAtThisLevel << 1;
+ assert(mipLevel > 0);
+ assert(widthAtPrevLevel >= info.blockWidth && heightAtPrevLevel >= info.blockHeight);
+ const modifiedWidth = widthAtPrevLevel - info.blockWidth << mipLevel - 1;
+ const modifiedHeight = heightAtPrevLevel - info.blockHeight << mipLevel - 1;
+
+ const modifyWidth = info.blockWidth > 1 && modifiedWidth !== textureSize[0];
+ const modifyHeight = info.blockHeight > 1 && modifiedHeight !== textureSize[1];
+
+ if (modifyWidth) {
+ yield [modifiedWidth, textureSize[1], textureSize[2]];
+ }
+ if (modifyHeight) {
+ yield [textureSize[0], modifiedHeight, textureSize[2]];
+ }
+ if (modifyWidth && modifyHeight) {
+ yield [modifiedWidth, modifiedHeight, textureSize[2]];
+ }
+
+ if (dimension === '3d') {
+ yield [textureSize[0], textureSize[1], textureSize[2] + 1];
+ }
+}
+
+g.test('mip_levels').
+desc(
+ `Test that copying various mip levels works. Covers two special code paths:
+ - The physical size of the subresource is not equal to the logical size.
+ - bufferSize - offset < bytesPerImage * copyExtent.depthOrArrayLayers, and copyExtent needs to be clamped for all block formats.
+ - For 3D textures test copying to a sub-range of the depth.
+
+Tests both 2D and 3D textures. 1D textures are skipped because they can only have one mip level.
+
+TODO: Make a variant for depth-stencil formats.
+ `
+).
+params((u) =>
+u.
+combineWithParams(kMethodsToTest).
+combine('format', kColorTextureFormats).
+filter(formatCanBeTested).
+combine('dimension', ['2d', '3d']).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combineWithParams([
+// origin + copySize = texturePhysicalSizeAtMipLevel for all coordinates, 2d texture */
+{
+ copySizeInBlocks: { width: 5, height: 4, depthOrArrayLayers: 1 },
+ originInBlocks: { x: 3, y: 2, z: 0 },
+ _mipSizeInBlocks: { width: 8, height: 6, depthOrArrayLayers: 1 },
+ mipLevel: 1
+},
+// origin + copySize = texturePhysicalSizeAtMipLevel for all coordinates, 2d-array texture
+{
+ copySizeInBlocks: { width: 5, height: 4, depthOrArrayLayers: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _mipSizeInBlocks: { width: 8, height: 6, depthOrArrayLayers: 3 },
+ mipLevel: 2
+},
+// origin.x + copySize.width = texturePhysicalSizeAtMipLevel.width
+{
+ copySizeInBlocks: { width: 5, height: 4, depthOrArrayLayers: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _mipSizeInBlocks: { width: 8, height: 7, depthOrArrayLayers: 4 },
+ mipLevel: 3
+},
+// origin.y + copySize.height = texturePhysicalSizeAtMipLevel.height
+{
+ copySizeInBlocks: { width: 5, height: 4, depthOrArrayLayers: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _mipSizeInBlocks: { width: 9, height: 6, depthOrArrayLayers: 4 },
+ mipLevel: 4
+},
+// origin.z + copySize.depthOrArrayLayers = texturePhysicalSizeAtMipLevel.depthOrArrayLayers
+{
+ copySizeInBlocks: { width: 5, height: 4, depthOrArrayLayers: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _mipSizeInBlocks: { width: 9, height: 7, depthOrArrayLayers: 3 },
+ mipLevel: 4
+},
+// origin + copySize < texturePhysicalSizeAtMipLevel for all coordinates
+{
+ copySizeInBlocks: { width: 5, height: 4, depthOrArrayLayers: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _mipSizeInBlocks: { width: 9, height: 7, depthOrArrayLayers: 4 },
+ mipLevel: 4
+}]
+).
+expand('textureSize', generateTestTextureSizes)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ copySizeInBlocks,
+ originInBlocks,
+ textureSize,
+ mipLevel,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const origin = {
+ x: originInBlocks.x * info.blockWidth,
+ y: originInBlocks.y * info.blockHeight,
+ z: originInBlocks.z
+ };
+ const copySize = {
+ width: copySizeInBlocks.width * info.blockWidth,
+ height: copySizeInBlocks.height * info.blockHeight,
+ depthOrArrayLayers: copySizeInBlocks.depthOrArrayLayers
+ };
+
+ const rowsPerImage = copySizeInBlocks.height + 1;
+ const bytesPerRow = align(copySize.width, 256);
+
+ const dataSize = dataBytesForCopyOrFail({
+ layout: { offset: 0, bytesPerRow, rowsPerImage },
+ format,
+ copySize,
+ method: initMethod
+ });
+
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ origin,
+ mipLevel,
+ textureSize,
+ format,
+ dimension,
+ initMethod,
+ checkMethod
+ });
+});
+
+const UND = undefined;
+g.test('undefined_params').
+desc(
+ `Tests undefined values of bytesPerRow, rowsPerImage, and origin.x/y/z.
+ Ensures bytesPerRow/rowsPerImage=undefined are valid and behave as expected.
+ Ensures origin.x/y/z undefined default to 0.`
+).
+params((u) =>
+u.
+combineWithParams(kMethodsToTest).
+combine('dimension', kTextureDimensions).
+beginSubcases().
+combineWithParams([
+// copying one row: bytesPerRow and rowsPerImage can be undefined
+{ copySize: [3, 1, 1], origin: [UND, UND, UND], bytesPerRow: UND, rowsPerImage: UND },
+// copying one slice: rowsPerImage can be undefined
+{ copySize: [3, 1, 1], origin: [UND, UND, UND], bytesPerRow: 256, rowsPerImage: UND },
+{ copySize: [3, 3, 1], origin: [UND, UND, UND], bytesPerRow: 256, rowsPerImage: UND },
+// copying two slices
+{ copySize: [3, 3, 2], origin: [UND, UND, UND], bytesPerRow: 256, rowsPerImage: 3 },
+// origin.x = undefined
+{ copySize: [1, 1, 1], origin: [UND, 1, 1], bytesPerRow: UND, rowsPerImage: UND },
+// origin.y = undefined
+{ copySize: [1, 1, 1], origin: [1, UND, 1], bytesPerRow: UND, rowsPerImage: UND },
+// origin.z = undefined
+{ copySize: [1, 1, 1], origin: [1, 1, UND], bytesPerRow: UND, rowsPerImage: UND }]
+).
+expandWithParams((p) => [
+{
+ _textureSize: [
+ 100,
+ p.copySize[1] + (p.origin[1] ?? 0),
+ p.copySize[2] + (p.origin[2] ?? 0)]
+
+}]
+).
+unless((p) => p.dimension === '1d' && (p._textureSize[1] > 1 || p._textureSize[2] > 1))
+).
+fn((t) => {
+ const {
+ dimension,
+ _textureSize,
+ bytesPerRow,
+ rowsPerImage,
+ copySize,
+ origin,
+ initMethod,
+ checkMethod
+ } = t.params;
+
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: {
+ offset: 0,
+ // Zero will get turned back into undefined later.
+ bytesPerRow: bytesPerRow ?? 0,
+ // Zero will get turned back into undefined later.
+ rowsPerImage: rowsPerImage ?? 0
+ },
+ copySize: { width: copySize[0], height: copySize[1], depthOrArrayLayers: copySize[2] },
+ dataSize: 2000,
+ textureSize: _textureSize,
+ // Zeros will get turned back into undefined later.
+ origin: { x: origin[0] ?? 0, y: origin[1] ?? 0, z: origin[2] ?? 0 },
+ format: 'rgba8unorm',
+ dimension,
+ initMethod,
+ checkMethod,
+ changeBeforePass: 'undefined'
+ });
+});
+
+function CopyMethodSupportedWithDepthStencilFormat(
+aspect,
+format,
+copyMethod)
+{
+ {
+ return (
+ aspect === 'stencil-only' && !!kTextureFormatInfo[format].stencil ||
+ aspect === 'depth-only' &&
+ !!kTextureFormatInfo[format].depth &&
+ copyMethod === 'CopyT2B' &&
+ depthStencilBufferTextureCopySupported('CopyT2B', format, aspect));
+
+ }
+}
+
+g.test('rowsPerImage_and_bytesPerRow_depth_stencil').
+desc(
+ `Test that copying data with various bytesPerRow and rowsPerImage values and minimum required
+bytes in copy works for copyBufferToTexture(), copyTextureToBuffer() and writeTexture() with stencil
+aspect and copyTextureToBuffer() with depth aspect.
+
+ Covers a special code path for Metal:
+ bufferSize - offset < bytesPerImage * copyExtent.depthOrArrayLayers
+ Covers a special code path for D3D12:
+ when bytesPerRow is not a multiple of 512 and copyExtent.depthOrArrayLayers > 1:
+ copyExtent.depthOrArrayLayers % 2 == { 0, 1 }
+ bytesPerRow == bytesInACompleteCopyImage
+ `
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+combine('copyMethod', ['WriteTexture', 'CopyB2T', 'CopyT2B']).
+combine('aspect', ['depth-only', 'stencil-only']).
+filter((t) => CopyMethodSupportedWithDepthStencilFormat(t.aspect, t.format, t.copyMethod)).
+beginSubcases().
+combineWithParams(kRowsPerImageAndBytesPerRowParams.paddings).
+combineWithParams(kRowsPerImageAndBytesPerRowParams.copySizes).
+filter((t) => {
+ return t.copyWidthInBlocks * t.copyHeightInBlocks * t.copyDepth > 0;
+}).
+combine('mipLevel', [0, 2])
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ format,
+ copyMethod,
+ aspect,
+ bytesPerRowPadding,
+ rowsPerImagePadding,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ mipLevel
+ } = t.params;
+ const bytesPerBlock = depthStencilFormatAspectSize(format, aspect);
+ const rowsPerImage = copyHeightInBlocks + rowsPerImagePadding;
+
+ const bytesPerRowAlignment = copyMethod === 'WriteTexture' ? 1 : kBytesPerRowAlignment;
+ const bytesPerRow =
+ align(bytesPerBlock * copyWidthInBlocks, bytesPerRowAlignment) +
+ bytesPerRowPadding * bytesPerRowAlignment;
+
+ const copySize = [copyWidthInBlocks, copyHeightInBlocks, copyDepth];
+ const textureSize = [
+ copyWidthInBlocks << mipLevel,
+ copyHeightInBlocks << mipLevel,
+ copyDepth];
+
+ if (copyMethod === 'CopyT2B') {
+ if (aspect === 'depth-only') {
+ t.DoCopyTextureToBufferWithDepthAspectTest(
+ format,
+ copySize,
+ bytesPerRowPadding,
+ rowsPerImagePadding,
+ 0,
+ 0,
+ mipLevel
+ );
+ } else {
+ t.DoCopyFromStencilTest(format, textureSize, bytesPerRow, rowsPerImage, 0, mipLevel);
+ }
+ } else {
+ assert(
+ aspect === 'stencil-only' && (copyMethod === 'CopyB2T' || copyMethod === 'WriteTexture')
+ );
+ const initialDataSize = dataBytesForCopyOrFail({
+ layout: { bytesPerRow, rowsPerImage },
+ format: 'stencil8',
+ copySize,
+ method: copyMethod
+ });
+
+ t.DoUploadToStencilTest(
+ format,
+ textureSize,
+ copyMethod,
+ bytesPerRow,
+ rowsPerImage,
+ initialDataSize,
+ 0,
+ mipLevel
+ );
+ }
+});
+
+g.test('offsets_and_sizes_copy_depth_stencil').
+desc(
+ `Test that copying data with various offset values and additional data paddings
+works for copyBufferToTexture(), copyTextureToBuffer() and writeTexture() with stencil aspect and
+copyTextureToBuffer() with depth aspect.
+
+ Covers two special code paths for D3D12:
+ offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
+ offset > bytesInACompleteCopyImage
+`
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+combine('copyMethod', ['WriteTexture', 'CopyB2T', 'CopyT2B']).
+combine('aspect', ['depth-only', 'stencil-only']).
+filter((t) => CopyMethodSupportedWithDepthStencilFormat(t.aspect, t.format, t.copyMethod)).
+beginSubcases().
+combineWithParams(kOffsetsAndSizesParams.offsetsAndPaddings).
+filter((t) => t.offsetInBlocks % 4 === 0).
+combine('copyDepth', kOffsetsAndSizesParams.copyDepth).
+combine('mipLevel', [0, 2])
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, copyMethod, aspect, offsetInBlocks, dataPaddingInBytes, copyDepth, mipLevel } =
+ t.params;
+ const bytesPerBlock = depthStencilFormatAspectSize(format, aspect);
+ const initialDataOffset = offsetInBlocks * bytesPerBlock;
+ const copySize = [3, 3, copyDepth];
+ const rowsPerImage = 3;
+ const bytesPerRow = 256;
+
+ const textureSize = [copySize[0] << mipLevel, copySize[1] << mipLevel, copyDepth];
+ if (copyMethod === 'CopyT2B') {
+ if (aspect === 'depth-only') {
+ t.DoCopyTextureToBufferWithDepthAspectTest(format, copySize, 0, 0, 0, 0, mipLevel);
+ } else {
+ t.DoCopyFromStencilTest(
+ format,
+ textureSize,
+ bytesPerRow,
+ rowsPerImage,
+ initialDataOffset,
+ mipLevel
+ );
+ }
+ } else {
+ assert(
+ aspect === 'stencil-only' && (copyMethod === 'CopyB2T' || copyMethod === 'WriteTexture')
+ );
+ const minDataSize = dataBytesForCopyOrFail({
+ layout: { offset: initialDataOffset, bytesPerRow, rowsPerImage },
+ format: 'stencil8',
+ copySize,
+ method: copyMethod
+ });
+ const initialDataSize = minDataSize + dataPaddingInBytes;
+ t.DoUploadToStencilTest(
+ format,
+ textureSize,
+ copyMethod,
+ bytesPerRow,
+ rowsPerImage,
+ initialDataSize,
+ initialDataOffset,
+ mipLevel
+ );
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/programmable_state_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/programmable_state_test.js
new file mode 100644
index 0000000000..09d5b988d4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/programmable_state_test.js
@@ -0,0 +1,157 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { unreachable } from '../../../../../common/util/util.js';import { GPUTest } from '../../../../gpu_test.js';
+
+
+
+
+
+
+
+export class ProgrammableStateTest extends GPUTest {
+ commonBindGroupLayouts = new Map();
+
+ getBindGroupLayout(type) {
+ if (!this.commonBindGroupLayouts.has(type)) {
+ this.commonBindGroupLayouts.set(
+ type,
+ this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
+ buffer: { type }
+ }]
+
+ })
+ );
+ }
+ return this.commonBindGroupLayouts.get(type);
+ }
+
+ getBindGroupLayouts(indices) {
+ const bindGroupLayouts = [];
+ bindGroupLayouts[indices.a] = this.getBindGroupLayout('read-only-storage');
+ bindGroupLayouts[indices.b] = this.getBindGroupLayout('read-only-storage');
+ bindGroupLayouts[indices.out] = this.getBindGroupLayout('storage');
+ return bindGroupLayouts;
+ }
+
+ createBindGroup(buffer, type) {
+ return this.device.createBindGroup({
+ layout: this.getBindGroupLayout(type),
+ entries: [{ binding: 0, resource: { buffer } }]
+ });
+ }
+
+ setBindGroup(
+ encoder,
+ index,
+ factory)
+ {
+ encoder.setBindGroup(index, factory(index));
+ }
+
+ // Create a compute pipeline that performs an operation on data from two bind groups,
+ // then writes the result to a third bind group.
+ createBindingStatePipeline(
+ encoderType,
+ groups,
+ algorithm = 'a.value - b.value')
+ {
+ switch (encoderType) {
+ case 'compute pass':{
+ const wgsl = `struct Data {
+ value : i32
+ };
+
+ @group(${groups.a}) @binding(0) var<storage> a : Data;
+ @group(${groups.b}) @binding(0) var<storage> b : Data;
+ @group(${groups.out}) @binding(0) var<storage, read_write> out : Data;
+
+ @compute @workgroup_size(1) fn main() {
+ out.value = ${algorithm};
+ return;
+ }
+ `;
+
+ return this.device.createComputePipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: this.getBindGroupLayouts(groups)
+ }),
+ compute: {
+ module: this.device.createShaderModule({
+ code: wgsl
+ }),
+ entryPoint: 'main'
+ }
+ });
+ }
+ case 'render pass':
+ case 'render bundle':{
+ const wgslShaders = {
+ vertex: `
+ @vertex fn vert_main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.5, 0.5, 0.0, 1.0);
+ }
+ `,
+
+ fragment: `
+ struct Data {
+ value : i32
+ };
+
+ @group(${groups.a}) @binding(0) var<storage> a : Data;
+ @group(${groups.b}) @binding(0) var<storage> b : Data;
+ @group(${groups.out}) @binding(0) var<storage, read_write> out : Data;
+
+ @fragment fn frag_main() -> @location(0) vec4<f32> {
+ out.value = ${algorithm};
+ return vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }
+ `
+ };
+
+ return this.device.createRenderPipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: this.getBindGroupLayouts(groups)
+ }),
+ vertex: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.vertex
+ }),
+ entryPoint: 'vert_main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.fragment
+ }),
+ entryPoint: 'frag_main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+ }
+ default:
+ unreachable();
+ }
+ }
+
+ setPipeline(pass, pipeline) {
+ if (pass instanceof GPUComputePassEncoder) {
+ pass.setPipeline(pipeline);
+ } else if (pass instanceof GPURenderPassEncoder || pass instanceof GPURenderBundleEncoder) {
+ pass.setPipeline(pipeline);
+ }
+ }
+
+ dispatchOrDraw(pass) {
+ if (pass instanceof GPUComputePassEncoder) {
+ pass.dispatchWorkgroups(1);
+ } else if (pass instanceof GPURenderPassEncoder) {
+ pass.draw(1);
+ } else if (pass instanceof GPURenderBundleEncoder) {
+ pass.draw(1);
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/state_tracking.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/state_tracking.spec.js
new file mode 100644
index 0000000000..865704bdd5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/programmable/state_tracking.spec.js
@@ -0,0 +1,306 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Ensure state is set correctly. Tries to stress state caching (setting different states multiple
+times in different orders) for setBindGroup and setPipeline.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUConst } from '../../../../constants.js';
+import { kProgrammableEncoderTypes } from '../../../../util/command_buffer_maker.js';
+
+import { ProgrammableStateTest } from './programmable_state_test.js';
+
+export const g = makeTestGroup(ProgrammableStateTest);
+
+const kBufferUsage = GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.STORAGE;
+
+g.test('bind_group_indices').
+desc(
+ `
+ Test that bind group indices can be declared in any order, regardless of their order in the shader.
+ - Test places the value of buffer a - buffer b into the out buffer, then reads the result.
+ `
+).
+params((u) =>
+u //
+.combine('encoderType', kProgrammableEncoderTypes).
+beginSubcases().
+combine('groupIndices', [
+{ a: 0, b: 1, out: 2 },
+{ a: 1, b: 2, out: 0 },
+{ a: 2, b: 0, out: 1 },
+{ a: 0, b: 2, out: 1 },
+{ a: 2, b: 1, out: 0 },
+{ a: 1, b: 0, out: 2 }]
+)
+).
+fn((t) => {
+ const { encoderType, groupIndices } = t.params;
+
+ const pipeline = t.createBindingStatePipeline(encoderType, groupIndices);
+
+ const out = t.makeBufferWithContents(new Int32Array([0]), kBufferUsage);
+ const bindGroups = {
+ a: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([3]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ b: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([2]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ out: t.createBindGroup(out, 'storage')
+ };
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+
+ t.setPipeline(encoder, pipeline);
+ encoder.setBindGroup(groupIndices.a, bindGroups.a);
+ encoder.setBindGroup(groupIndices.b, bindGroups.b);
+ encoder.setBindGroup(groupIndices.out, bindGroups.out);
+ t.dispatchOrDraw(encoder);
+ validateFinishAndSubmit(true, true);
+
+ t.expectGPUBufferValuesEqual(out, new Int32Array([1]));
+});
+
+g.test('bind_group_order').
+desc(
+ `
+ Test that the order in which you set the bind groups doesn't matter.
+ `
+).
+params((u) =>
+u //
+.combine('encoderType', kProgrammableEncoderTypes).
+beginSubcases().
+combine('setOrder', [
+['a', 'b', 'out'],
+['b', 'out', 'a'],
+['out', 'a', 'b'],
+['b', 'a', 'out'],
+['a', 'out', 'b'],
+['out', 'b', 'a']]
+)
+).
+fn((t) => {
+ const { encoderType, setOrder } = t.params;
+
+ const groupIndices = { a: 0, b: 1, out: 2 };
+ const pipeline = t.createBindingStatePipeline(encoderType, groupIndices);
+
+ const out = t.makeBufferWithContents(new Int32Array([0]), kBufferUsage);
+ const bindGroups = {
+ a: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([3]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ b: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([2]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ out: t.createBindGroup(out, 'storage')
+ };
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+ t.setPipeline(encoder, pipeline);
+
+ for (const bindingName of setOrder) {
+ encoder.setBindGroup(groupIndices[bindingName], bindGroups[bindingName]);
+ }
+
+ t.dispatchOrDraw(encoder);
+ validateFinishAndSubmit(true, true);
+
+ t.expectGPUBufferValuesEqual(out, new Int32Array([1]));
+});
+
+g.test('bind_group_before_pipeline').
+desc(
+ `
+ Test that setting bind groups prior to setting the pipeline is still valid.
+ `
+).
+params((u) =>
+u //
+.combine('encoderType', kProgrammableEncoderTypes).
+beginSubcases().
+combineWithParams([
+{ setBefore: ['a', 'b'], setAfter: ['out'] },
+{ setBefore: ['a'], setAfter: ['b', 'out'] },
+{ setBefore: ['out', 'b'], setAfter: ['a'] },
+{ setBefore: ['a', 'b', 'out'], setAfter: [] }]
+)
+).
+fn((t) => {
+ const { encoderType, setBefore, setAfter } = t.params;
+ const groupIndices = { a: 0, b: 1, out: 2 };
+ const pipeline = t.createBindingStatePipeline(encoderType, groupIndices);
+
+ const out = t.makeBufferWithContents(new Int32Array([0]), kBufferUsage);
+ const bindGroups = {
+ a: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([3]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ b: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([2]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ out: t.createBindGroup(out, 'storage')
+ };
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+
+ for (const bindingName of setBefore) {
+ encoder.setBindGroup(groupIndices[bindingName], bindGroups[bindingName]);
+ }
+
+ t.setPipeline(encoder, pipeline);
+
+ for (const bindingName of setAfter) {
+ encoder.setBindGroup(groupIndices[bindingName], bindGroups[bindingName]);
+ }
+
+ t.dispatchOrDraw(encoder);
+ validateFinishAndSubmit(true, true);
+
+ t.expectGPUBufferValuesEqual(out, new Int32Array([1]));
+});
+
+g.test('one_bind_group_multiple_slots').
+desc(
+ `
+ Test that a single bind group may be bound to more than one slot.
+ `
+).
+params((u) =>
+u //
+.combine('encoderType', kProgrammableEncoderTypes)
+).
+fn((t) => {
+ const { encoderType } = t.params;
+ const pipeline = t.createBindingStatePipeline(encoderType, { a: 0, b: 1, out: 2 });
+
+ const out = t.makeBufferWithContents(new Int32Array([1]), kBufferUsage);
+ const bindGroups = {
+ ab: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([3]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ out: t.createBindGroup(out, 'storage')
+ };
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+ t.setPipeline(encoder, pipeline);
+
+ encoder.setBindGroup(0, bindGroups.ab);
+ encoder.setBindGroup(1, bindGroups.ab);
+ encoder.setBindGroup(2, bindGroups.out);
+
+ t.dispatchOrDraw(encoder);
+ validateFinishAndSubmit(true, true);
+
+ t.expectGPUBufferValuesEqual(out, new Int32Array([0]));
+});
+
+g.test('bind_group_multiple_sets').
+desc(
+ `
+ Test that the last bind group set to a given slot is used when dispatching.
+ `
+).
+params((u) =>
+u //
+.combine('encoderType', kProgrammableEncoderTypes)
+).
+fn((t) => {
+ const { encoderType } = t.params;
+ const pipeline = t.createBindingStatePipeline(encoderType, { a: 0, b: 1, out: 2 });
+
+ const badOut = t.makeBufferWithContents(new Int32Array([-1]), kBufferUsage);
+ const out = t.makeBufferWithContents(new Int32Array([0]), kBufferUsage);
+ const bindGroups = {
+ a: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([3]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ b: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([2]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ c: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([5]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ badOut: t.createBindGroup(badOut, 'storage'),
+ out: t.createBindGroup(out, 'storage')
+ };
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+
+ encoder.setBindGroup(1, bindGroups.c);
+
+ t.setPipeline(encoder, pipeline);
+
+ encoder.setBindGroup(0, bindGroups.c);
+ encoder.setBindGroup(0, bindGroups.a);
+
+ encoder.setBindGroup(2, bindGroups.badOut);
+
+ encoder.setBindGroup(1, bindGroups.b);
+ encoder.setBindGroup(2, bindGroups.out);
+
+ t.dispatchOrDraw(encoder);
+ validateFinishAndSubmit(true, true);
+
+ t.expectGPUBufferValuesEqual(out, new Int32Array([1]));
+ t.expectGPUBufferValuesEqual(badOut, new Int32Array([-1]));
+});
+
+g.test('compatible_pipelines').
+desc('Test that bind groups can be shared between compatible pipelines.').
+params((u) =>
+u //
+.combine('encoderType', kProgrammableEncoderTypes)
+).
+fn((t) => {
+ const { encoderType } = t.params;
+ const pipelineA = t.createBindingStatePipeline(encoderType, { a: 0, b: 1, out: 2 });
+ const pipelineB = t.createBindingStatePipeline(
+ encoderType,
+ { a: 0, b: 1, out: 2 },
+ 'a.value + b.value'
+ );
+
+ const outA = t.makeBufferWithContents(new Int32Array([0]), kBufferUsage);
+ const outB = t.makeBufferWithContents(new Int32Array([0]), kBufferUsage);
+ const bindGroups = {
+ a: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([3]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ b: t.createBindGroup(
+ t.makeBufferWithContents(new Int32Array([2]), kBufferUsage),
+ 'read-only-storage'
+ ),
+ outA: t.createBindGroup(outA, 'storage'),
+ outB: t.createBindGroup(outB, 'storage')
+ };
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+ encoder.setBindGroup(0, bindGroups.a);
+ encoder.setBindGroup(1, bindGroups.b);
+
+ t.setPipeline(encoder, pipelineA);
+ encoder.setBindGroup(2, bindGroups.outA);
+ t.dispatchOrDraw(encoder);
+
+ t.setPipeline(encoder, pipelineB);
+ encoder.setBindGroup(2, bindGroups.outB);
+ t.dispatchOrDraw(encoder);
+
+ validateFinishAndSubmit(true, true);
+
+ t.expectGPUBufferValuesEqual(outA, new Int32Array([1]));
+ t.expectGPUBufferValuesEqual(outB, new Int32Array([5]));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.js
new file mode 100644
index 0000000000..716b59fd6f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.js
@@ -0,0 +1,1033 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+API operations tests for occlusion queries.
+
+- test query with
+ - scissor
+ - sample mask
+ - alpha to coverage
+ - stencil
+ - depth test
+- test empty query (no draw) (should be cleared?)
+- test via render bundle
+- test resolveQuerySet with non-zero firstIndex
+- test no queries is zero
+- test 0x0 -> 0x3 sample mask
+- test 0 -> 1 alpha to coverage
+- test resolving twice in same pass keeps values
+- test resolving twice across pass keeps values
+- test resolveQuerySet destinationOffset
+`;import { kUnitCaseParamsBuilder } from '../../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import {
+ assert,
+
+ range,
+ unreachable } from
+'../../../../../common/util/util.js';
+import { kMaxQueryCount } from '../../../../capability_info.js';
+
+import { GPUTest } from '../../../../gpu_test.js';
+
+const kRequiredQueryBufferOffsetAlignment = 256;
+const kBytesPerQuery = 8;
+const kTextureSize = [4, 4];
+
+const kRenderModes = ['direct', 'render-bundle'];
+
+
+const kBufferOffsets = ['zero', 'non-zero'];
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// MAINTENANCE_TODO: Refactor these helper classes to use GPUTestBase.createEncoder
+//
+// The refactor would require some new features in CommandBufferMaker such as:
+//
+// * Multi render bundle in single render pass support
+//
+// * Some way to allow calling render pass commands on render bundle encoder.
+// Potentially have a special abstract encoder that wraps the two and defers
+// relevant calls appropriately.
+
+/**
+ * This class is used by the RenderPassHelper below to
+ * abstract calling these 4 functions on a RenderPassEncoder or a RenderBundleEncoder.
+ */
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * This class helps use a render pass encoder or a render bundle encoder
+ * in the correct way given the order that operations must happen, in order to be
+ * compatible across both paths.
+ */
+class RenderPassHelper {
+
+
+
+
+ constructor(pass, helper) {
+ this._pass = pass;
+ this._helper = helper;
+ }
+ setScissorRect(x, y, width, height) {
+ assert(!this._queryHelper);
+ this._pass.setScissorRect(x, y, width, height);
+ }
+ setStencilReference(ref) {
+ assert(!this._queryHelper);
+ this._pass.setStencilReference(ref);
+ }
+ beginOcclusionQuery(queryIndex) {
+ assert(!this._queryHelper);
+ this._pass.beginOcclusionQuery(queryIndex);
+ this._queryHelper = this._helper.begin(() => {
+ assert(!!this._queryHelper);
+ this._queryHelper = undefined;
+ this._pass.endOcclusionQuery();
+ });
+ return this._queryHelper;
+ }
+}
+
+/**
+ * Helper class for using a render pass encoder directly
+ */
+class QueryHelperDirect {
+
+
+
+ constructor(pass, endFn) {
+ this._pass = pass;
+ this._endFn = endFn;
+ }
+ setPipeline(pipeline) {
+ assert(!!this._pass);
+ this._pass.setPipeline(pipeline);
+ }
+ setVertexBuffer(buffer) {
+ assert(!!this._pass);
+ this._pass.setVertexBuffer(0, buffer);
+ }
+ draw(count) {
+ assert(!!this._pass);
+ this._pass.draw(count);
+ }
+ end() {
+ // make this object impossible to use after calling end
+ const fn = this._endFn;
+ this._endFn = unreachable;
+ this._pass = undefined;
+ fn();
+ }
+}
+
+/**
+ * Helper class for starting a query on a render pass encoder directly
+ */
+class QueryStarterDirect {
+
+
+
+ constructor(pass) {
+ this._pass = pass;
+ }
+ begin(endFn) {
+ assert(!this._helper);
+ this._helper = new QueryHelperDirect(this._pass, () => {
+ this._helper = undefined;
+ endFn();
+ });
+ return this._helper;
+ }
+}
+
+/**
+ * Helper class for using a render bundle encoder.
+ */
+class QueryHelperRenderBundle {
+
+
+
+ constructor(pass, endFn) {
+ this._encoder = pass;
+ this._endFn = endFn;
+ }
+ setPipeline(pipeline) {
+ assert(!!this._encoder);
+ this._encoder.setPipeline(pipeline);
+ }
+ setVertexBuffer(buffer) {
+ assert(!!this._encoder);
+ this._encoder.setVertexBuffer(0, buffer);
+ }
+ draw(count) {
+ assert(!!this._encoder);
+ this._encoder.draw(count);
+ }
+ end() {
+ // make this object impossible to use after calling end
+ const fn = this._endFn;
+ this._endFn = unreachable;
+ this._encoder = undefined;
+ fn();
+ }
+}
+
+/**
+ * Helper class for starting a query on a render bundle encoder
+ */
+class QueryStarterRenderBundle {
+
+
+
+
+
+
+ constructor(
+ device,
+ pass,
+ renderPassDescriptor)
+ {
+ this._device = device;
+ this._pass = pass;
+ const colorAttachment =
+ renderPassDescriptor.colorAttachments[
+ 0];
+ this._renderBundleEncoderDescriptor = {
+ colorFormats: ['rgba8unorm'],
+ depthStencilFormat: renderPassDescriptor.depthStencilAttachment?.depthLoadOp ?
+ 'depth24plus' :
+ renderPassDescriptor.depthStencilAttachment?.stencilLoadOp ?
+ 'stencil8' :
+ undefined,
+ sampleCount: colorAttachment.resolveTarget ? 4 : 1
+ };
+ }
+ begin(endFn) {
+ assert(!this._encoder);
+ this._encoder = this._device.createRenderBundleEncoder(this._renderBundleEncoderDescriptor);
+ this._helper = new QueryHelperRenderBundle(this._encoder, () => {
+ assert(!!this._encoder);
+ assert(!!this._helper);
+ this._pass.executeBundles([this._encoder.finish()]);
+ this._helper = undefined;
+ this._encoder = undefined;
+ endFn();
+ });
+ return this._helper;
+ }
+ setPipeline(pipeline) {
+ assert(!!this._encoder);
+ this._encoder.setPipeline(pipeline);
+ }
+ setVertexBuffer(buffer) {
+ assert(!!this._encoder);
+ this._encoder.setVertexBuffer(0, buffer);
+ }
+ draw(count) {
+ assert(!!this._encoder);
+ this._encoder.draw(count);
+ }
+}
+
+class OcclusionQueryTest extends GPUTest {
+ createBuffer(desc) {
+ return this.trackForCleanup(this.device.createBuffer(desc));
+ }
+ createTexture(desc) {
+ return this.trackForCleanup(this.device.createTexture(desc));
+ }
+ createQuerySet(desc) {
+ return this.trackForCleanup(this.device.createQuerySet(desc));
+ }
+ createVertexBuffer(data) {
+ return this.makeBufferWithContents(data, GPUBufferUsage.VERTEX);
+ }
+ createSingleTriangleVertexBuffer(z) {
+
+ return this.createVertexBuffer(new Float32Array([
+ -0.5, -0.5, z,
+ 0.5, -0.5, z,
+ -0.5, 0.5, z]
+ ));
+ }
+ async readBufferAsBigUint64(buffer) {
+ await buffer.mapAsync(GPUMapMode.READ);
+ const result = new BigUint64Array(buffer.getMappedRange().slice(0));
+ buffer.unmap();
+ return result;
+ }
+ setup(params) {
+ const {
+ numQueries,
+ depthStencilFormat,
+ sampleMask = 0xffffffff,
+ alpha,
+ sampleCount,
+ writeMask = 0xf,
+ bufferOffset,
+ renderMode
+ } = params;
+ const { device } = this;
+
+ const queryResolveBufferOffset =
+ bufferOffset === 'non-zero' ? kRequiredQueryBufferOffsetAlignment : 0;
+ const queryResolveBuffer = this.createBuffer({
+ size: numQueries * 8 + queryResolveBufferOffset,
+ usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC
+ });
+
+ const readBuffer = this.createBuffer({
+ size: numQueries * kBytesPerQuery,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
+ });
+
+ const vertexBuffer = this.createSingleTriangleVertexBuffer(0);
+
+ const renderTargetTexture = this.createTexture({
+ format: 'rgba8unorm',
+ size: kTextureSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const multisampleRenderTarget = sampleCount ?
+ this.createTexture({
+ size: kTextureSize,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount
+ }) :
+ null;
+
+ const depthStencilTexture = depthStencilFormat ?
+ this.createTexture({
+ format: depthStencilFormat,
+ size: kTextureSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ }) :
+ undefined;
+
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs(@location(0) pos: vec4f) -> @builtin(position) vec4f {
+ return pos;
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0, 0, 0, ${alpha === undefined ? 1 : alpha});
+ }
+ `
+ });
+
+ const haveDepth = !!depthStencilFormat && depthStencilFormat.includes('depth');
+ const haveStencil = !!depthStencilFormat && depthStencilFormat.includes('stencil');
+ assert(!(haveDepth && haveStencil), 'code does not handle mixed depth-stencil');
+
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ buffers: [
+ {
+ arrayStride: 3 * 4,
+ attributes: [
+ {
+ shaderLocation: 0,
+ offset: 0,
+ format: 'float32x3'
+ }]
+
+ }]
+
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: 'rgba8unorm', writeMask }]
+ },
+ ...(sampleCount && {
+ multisample: {
+ count: sampleCount,
+ mask: alpha === undefined ? sampleMask : 0xffffffff,
+ alphaToCoverageEnabled: alpha !== undefined
+ }
+ }),
+ ...(depthStencilTexture && {
+ depthStencil: {
+ format: depthStencilFormat,
+ depthWriteEnabled: haveDepth,
+ depthCompare: haveDepth ? 'less-equal' : 'always',
+ ...(haveStencil && {
+ stencilFront: {
+ compare: 'equal'
+ }
+ })
+ }
+ })
+ });
+
+ const querySetOffset = params?.querySetOffset === 'non-zero' ? 7 : 0;
+ const occlusionQuerySet = this.createQuerySet({
+ type: 'occlusion',
+ count: numQueries + querySetOffset
+ });
+
+ const renderPassDescriptor = {
+ colorAttachments: sampleCount ?
+ [
+ {
+ view: multisampleRenderTarget.createView(),
+ resolveTarget: renderTargetTexture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }] :
+
+ [
+ {
+ view: renderTargetTexture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ ...(haveDepth && {
+ depthStencilAttachment: {
+ view: depthStencilTexture.createView(),
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ depthClearValue: 0.5
+ }
+ }),
+ ...(haveStencil && {
+ depthStencilAttachment: {
+ view: depthStencilTexture.createView(),
+ stencilClearValue: 0,
+ stencilLoadOp: 'clear',
+ stencilStoreOp: 'store'
+ }
+ }),
+ occlusionQuerySet
+ };
+
+ return {
+ readBuffer,
+ vertexBuffer,
+ queryResolveBuffer,
+ queryResolveBufferOffset,
+ occlusionQuerySet,
+ renderTargetTexture,
+ renderPassDescriptor,
+ pipeline,
+ depthStencilTexture,
+ querySetOffset,
+ renderMode
+ };
+ }
+ async runQueryTest(
+ resources,
+ renderPassDescriptor,
+ encodePassFn,
+ checkQueryIndexResultFn)
+ {
+ const { device } = this;
+ const {
+ readBuffer,
+ queryResolveBuffer,
+ queryResolveBufferOffset,
+ occlusionQuerySet,
+ querySetOffset,
+ renderMode = 'direct'
+ } = resources;
+ const numQueries = occlusionQuerySet.count - querySetOffset;
+ const queryIndices = range(numQueries, (i) => i + querySetOffset);
+
+ const encoder = device.createCommandEncoder();
+ if (renderPassDescriptor) {
+ const pass = encoder.beginRenderPass(renderPassDescriptor);
+ const helper = new RenderPassHelper(
+ pass,
+ renderMode === 'direct' ?
+ new QueryStarterDirect(pass) :
+ new QueryStarterRenderBundle(device, pass, renderPassDescriptor)
+ );
+
+ for (const queryIndex of queryIndices) {
+ encodePassFn(helper, queryIndex);
+ }
+ pass.end();
+ }
+
+ encoder.resolveQuerySet(
+ occlusionQuerySet,
+ querySetOffset,
+ numQueries,
+ queryResolveBuffer,
+ queryResolveBufferOffset
+ );
+ encoder.copyBufferToBuffer(
+ queryResolveBuffer,
+ queryResolveBufferOffset,
+ readBuffer,
+ 0,
+ readBuffer.size
+ );
+ device.queue.submit([encoder.finish()]);
+
+ const result = await this.readBufferAsBigUint64(readBuffer);
+ for (const queryIndex of queryIndices) {
+ const resultNdx = queryIndex - querySetOffset;
+ const passed = !!result[resultNdx];
+ checkQueryIndexResultFn(passed, queryIndex);
+ }
+
+ return result;
+ }
+}
+
+const kQueryTestBaseParams = kUnitCaseParamsBuilder.
+combine('writeMask', [0xf, 0x0]).
+combine('renderMode', kRenderModes).
+combine('bufferOffset', kBufferOffsets).
+combine('querySetOffset', kBufferOffsets);
+
+export const g = makeTestGroup(OcclusionQueryTest);
+
+g.test('occlusion_query,initial').
+desc(`Test getting contents of QuerySet without any queries.`).
+fn(async (t) => {
+ const kNumQueries = kMaxQueryCount;
+ const resources = t.setup({ numQueries: kNumQueries });
+ await t.runQueryTest(
+ resources,
+ null,
+ () => {},
+ (passed) => {
+ t.expect(!passed);
+ }
+ );
+});
+
+g.test('occlusion_query,basic').
+desc('Test all queries pass').
+params(kQueryTestBaseParams).
+fn(async (t) => {
+ const { writeMask, renderMode, bufferOffset, querySetOffset } = t.params;
+ const kNumQueries = 30;
+ const resources = t.setup({
+ writeMask,
+ renderMode,
+ bufferOffset,
+ querySetOffset,
+ numQueries: kNumQueries
+ });
+ const { renderPassDescriptor, vertexBuffer, pipeline } = resources;
+
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ (helper, queryIndex) => {
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(vertexBuffer);
+ queryHelper.draw(3);
+ queryHelper.end();
+ },
+ (passed, queryIndex) => {
+ const expectPassed = true;
+ t.expect(
+ !!passed === expectPassed,
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
+ );
+ }
+ );
+});
+
+g.test('occlusion_query,empty').
+desc(
+ `
+ Test beginOcclusionQuery/endOcclusionQuery with nothing in between clears the queries
+
+ Calls beginOcclusionQuery/draw/endOcclusionQuery that should show passing fragments
+ and validates they passed. Then executes the same queries (same QuerySet) without drawing.
+ Those queries should have not passed.
+ `
+).
+fn(async (t) => {
+ const kNumQueries = 30;
+ const resources = t.setup({ numQueries: kNumQueries });
+ const { vertexBuffer, renderPassDescriptor, pipeline } = resources;
+
+ const makeQueryRunner = (draw) => {
+ return (helper, queryIndex) => {
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(vertexBuffer);
+ if (draw) {
+ queryHelper.draw(3);
+ }
+ queryHelper.end();
+ };
+ };
+
+ const makeQueryChecker = (draw) => {
+ return (passed, queryIndex) => {
+ const expectPassed = draw;
+ t.expect(
+ !!passed === expectPassed,
+ `draw: ${draw}, queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
+ );
+ };
+ };
+
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ makeQueryRunner(true),
+ makeQueryChecker(true)
+ );
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ makeQueryRunner(false),
+ makeQueryChecker(false)
+ );
+});
+
+g.test('occlusion_query,scissor').
+desc(
+ `
+ Test beginOcclusionQuery/endOcclusionQuery using scissor to occlude
+ `
+).
+params(kQueryTestBaseParams).
+fn(async (t) => {
+ const { writeMask, renderMode, bufferOffset, querySetOffset } = t.params;
+ const kNumQueries = 30;
+ const resources = t.setup({
+ writeMask,
+ renderMode,
+ bufferOffset,
+ querySetOffset,
+ numQueries: kNumQueries
+ });
+ const { renderPassDescriptor, renderTargetTexture, vertexBuffer, pipeline } = resources;
+
+ const getScissorRect = (i) => {
+ const { width, height } = renderTargetTexture;
+ switch (i % 4) {
+ case 0: // whole target
+ return {
+ x: 0,
+ y: 0,
+ width,
+ height,
+ occluded: false,
+ name: 'whole target'
+ };
+ case 1: // center
+ return {
+ x: width / 4,
+ y: height / 4,
+ width: width / 2,
+ height: height / 2,
+ occluded: false,
+ name: 'center'
+ };
+ case 2: // none
+ return {
+ x: width / 4,
+ y: height / 4,
+ width: 0,
+ height: 0,
+ occluded: true,
+ name: 'none'
+ };
+ case 3: // top 1/4
+ return {
+ x: 0,
+ y: 0,
+ width,
+ height: height / 2,
+ occluded: true,
+ name: 'top quarter'
+ };
+ default:
+ unreachable();
+ }
+ };
+
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ (helper, queryIndex) => {
+ const { x, y, width, height } = getScissorRect(queryIndex);
+ helper.setScissorRect(x, y, width, height);
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(vertexBuffer);
+ queryHelper.draw(3);
+ queryHelper.end();
+ },
+ (passed, queryIndex) => {
+ const { occluded, name: scissorCase } = getScissorRect(queryIndex);
+ const expectPassed = !occluded;
+ t.expect(
+ !!passed === expectPassed,
+ `queryIndex: ${queryIndex}, scissorCase: ${scissorCase}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ );
+ }
+ );
+});
+
+g.test('occlusion_query,depth').
+desc(
+ `
+ Test beginOcclusionQuery/endOcclusionQuery using depth test to occlude
+
+ Compares depth against 0.5, with alternating vertex buffers which have a depth
+ of 0 and 1. When depth check passes, we expect non-zero successful fragments.
+ `
+).
+params(kQueryTestBaseParams).
+fn(async (t) => {
+ const { writeMask, renderMode, bufferOffset, querySetOffset } = t.params;
+ const kNumQueries = 30;
+ const resources = t.setup({
+ writeMask,
+ renderMode,
+ bufferOffset,
+ querySetOffset,
+ numQueries: kNumQueries,
+ depthStencilFormat: 'depth24plus'
+ });
+ const { vertexBuffer: vertexBufferAtZ0, renderPassDescriptor, pipeline } = resources;
+ const vertexBufferAtZ1 = t.createSingleTriangleVertexBuffer(1);
+
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ (helper, queryIndex) => {
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(queryIndex % 2 ? vertexBufferAtZ1 : vertexBufferAtZ0);
+ queryHelper.draw(3);
+ queryHelper.end();
+ },
+ (passed, queryIndex) => {
+ const expectPassed = queryIndex % 2 === 0;
+ t.expect(
+ !!passed === expectPassed,
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ );
+ }
+ );
+});
+
+g.test('occlusion_query,stencil').
+desc(
+ `
+ Test beginOcclusionQuery/endOcclusionQuery using stencil to occlude
+
+ Compares stencil against 0, with alternating stencil reference values of
+ of 0 and 1. When stencil test passes, we expect non-zero successful fragments.
+ `
+).
+params(kQueryTestBaseParams).
+fn(async (t) => {
+ const { writeMask, renderMode, bufferOffset, querySetOffset } = t.params;
+ const kNumQueries = 30;
+ const resources = t.setup({
+ writeMask,
+ renderMode,
+ bufferOffset,
+ querySetOffset,
+ numQueries: kNumQueries,
+ depthStencilFormat: 'stencil8'
+ });
+ const { vertexBuffer, renderPassDescriptor, pipeline } = resources;
+
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ (helper, queryIndex) => {
+ helper.setStencilReference(queryIndex % 2);
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(vertexBuffer);
+ queryHelper.draw(3);
+ queryHelper.end();
+ },
+ (passed, queryIndex) => {
+ const expectPassed = queryIndex % 2 === 0;
+ t.expect(
+ !!passed === expectPassed,
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ );
+ }
+ );
+});
+
+g.test('occlusion_query,sample_mask').
+desc(
+ `
+ Test beginOcclusionQuery/endOcclusionQuery using sample_mask to occlude
+
+ Set sampleMask to 0, 2, 4, 6 and draw quads in top right or bottom left corners of the texel.
+ If the corner we draw to matches the corner masked we expect non-zero successful fragments.
+
+ See: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
+ `
+).
+params(kQueryTestBaseParams.combine('sampleMask', [0, 2, 4, 6])).
+fn(async (t) => {
+ const { writeMask, renderMode, bufferOffset, querySetOffset, sampleMask } = t.params;
+ const kNumQueries = 30;
+ const sampleCount = 4;
+ const resources = t.setup({
+ writeMask,
+ renderMode,
+ bufferOffset,
+ querySetOffset,
+ numQueries: kNumQueries,
+ sampleCount,
+ sampleMask
+ });
+ const { renderPassDescriptor, pipeline } = resources;
+
+ const createQuad = (offset) => {
+
+ return t.createVertexBuffer(new Float32Array([
+ offset + 0, offset + 0, 0,
+ offset + 0.25, offset + 0, 0,
+ offset + 0, offset + 0.25, 0,
+ offset + 0, offset + 0.25, 0,
+ offset + 0.25, offset + 0, 0,
+ offset + 0.25, offset + 0.25, 0]
+ ));
+ };
+
+ const vertexBufferBL = createQuad(0);
+ const vertexBufferTR = createQuad(0.25);
+
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ (helper, queryIndex) => {
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(queryIndex % 2 ? vertexBufferTR : vertexBufferBL);
+ queryHelper.draw(6);
+ queryHelper.end();
+ },
+ (passed, queryIndex) => {
+ // Above we draw to a specific corner (sample) of a multi-sampled texel
+ // drawMask is the "sampleMask" representation of that corner.
+ // In other words, if drawMask is 2 (we drew to the top right) and
+ // sampleMask is 2 (drawing is allowed to the top right) then we expect
+ // passing fragments.
+ const drawMask = queryIndex % 2 ? 2 : 4;
+ const expectPassed = !!(sampleMask & drawMask);
+ t.expect(
+ !!passed === expectPassed,
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ );
+ }
+ );
+});
+
+g.test('occlusion_query,alpha_to_coverage').
+desc(
+ `
+ Test beginOcclusionQuery/endOcclusionQuery using alphaToCoverage to occlude
+
+ Set alpha to 0, 0.25, 0.5, 0.75, and 1, draw quads in 4 corners of texel.
+ Some should be culled. We count how many passed via queries. It's undefined which
+ will pass but it is defined how many will pass for a given alpha value.
+
+ Note: It seems like the result is well defined but if we find some devices/drivers
+ don't follow this exactly then we can relax check for the expected number of passed
+ queries.
+
+ See: https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f
+ `
+).
+params(kQueryTestBaseParams.combine('alpha', [0, 0.25, 0.5, 0.75, 1.0])).
+fn(async (t) => {
+ const { writeMask, renderMode, bufferOffset, querySetOffset, alpha } = t.params;
+ const kNumQueries = 32;
+ const sampleCount = 4;
+ const resources = t.setup({
+ writeMask,
+ renderMode,
+ bufferOffset,
+ querySetOffset,
+ numQueries: kNumQueries,
+ sampleCount,
+ alpha
+ });
+ const { renderPassDescriptor, pipeline } = resources;
+
+ const createQuad = (xOffset, yOffset) => {
+
+ return t.createVertexBuffer(new Float32Array([
+ xOffset + 0, yOffset + 0, 0,
+ xOffset + 0.25, yOffset + 0, 0,
+ xOffset + 0, yOffset + 0.25, 0,
+ xOffset + 0, yOffset + 0.25, 0,
+ xOffset + 0.25, yOffset + 0, 0,
+ xOffset + 0.25, yOffset + 0.25, 0]
+ ));
+ };
+
+ const vertexBuffers = [
+ createQuad(0, 0),
+ createQuad(0.25, 0),
+ createQuad(0, 0.25),
+ createQuad(0.25, 0.25)];
+
+
+ const numPassedPerGroup = new Array(kNumQueries / 4).fill(0);
+
+ // These tests can't use queryIndex to decide what to draw because which mask
+ // a particular alpha converts to is implementation defined. When querySetOffset is
+ // non-zero the queryIndex will go 7, 8, 9, 10, ... but we need to guarantee
+ // 4 queries per pixel and group those results so `queryIndex / 4 | 0` won't work.
+ // Instead we count the queries to get 4 draws per group, one to each quadrant of a pixel
+ // Then we total up the passes for those 4 queries by queryCount.
+ let queryCount = 0;
+ let resultCount = 0;
+ await t.runQueryTest(
+ resources,
+ renderPassDescriptor,
+ (helper, queryIndex) => {
+ const queryHelper = helper.beginOcclusionQuery(queryIndex);
+ queryHelper.setPipeline(pipeline);
+ queryHelper.setVertexBuffer(vertexBuffers[queryCount++ % 4]);
+ queryHelper.draw(6);
+ queryHelper.end();
+ },
+ (passed) => {
+ const groupIndex = resultCount++ / 4 | 0;
+ numPassedPerGroup[groupIndex] += passed ? 1 : 0;
+ }
+ );
+
+ const expected = alpha / 0.25 | 0;
+ numPassedPerGroup.forEach((numPassed, queryGroup) => {
+ t.expect(
+ numPassed === expected,
+ `queryGroup: ${queryGroup}, was: ${numPassed}, expected: ${expected}`
+ );
+ });
+});
+
+g.test('occlusion_query,multi_resolve').
+desc('Test calling resolveQuerySet more than once does not change results').
+fn(async (t) => {
+ const { device } = t;
+ const kNumQueries = 30;
+ const {
+ pipeline,
+ vertexBuffer,
+ occlusionQuerySet,
+ renderPassDescriptor,
+ renderTargetTexture,
+ queryResolveBuffer,
+ readBuffer
+ } = t.setup({ numQueries: kNumQueries });
+
+ const readBuffer2 = t.createBuffer(readBuffer);
+ const readBuffer3 = t.createBuffer(readBuffer);
+
+ const renderSomething = (encoder) => {
+ const pass = encoder.beginRenderPass(renderPassDescriptor);
+ pass.setPipeline(pipeline);
+ pass.setVertexBuffer(0, vertexBuffer);
+ pass.setScissorRect(0, 0, renderTargetTexture.width, renderTargetTexture.height);
+ pass.draw(3);
+ pass.end();
+ };
+
+ {
+ const encoder = device.createCommandEncoder();
+ {
+ const pass = encoder.beginRenderPass(renderPassDescriptor);
+ pass.setPipeline(pipeline);
+ pass.setVertexBuffer(0, vertexBuffer);
+
+ for (let i = 0; i < kNumQueries; ++i) {
+ pass.beginOcclusionQuery(i);
+ if (i % 2) {
+ pass.setScissorRect(0, 0, renderTargetTexture.width, renderTargetTexture.height);
+ } else {
+ pass.setScissorRect(0, 0, 0, 0);
+ }
+ pass.draw(3);
+ pass.endOcclusionQuery();
+ }
+ pass.end();
+ }
+
+ // Intentionally call resolveQuerySet twice
+ encoder.resolveQuerySet(occlusionQuerySet, 0, kNumQueries, queryResolveBuffer, 0);
+ encoder.resolveQuerySet(occlusionQuerySet, 0, kNumQueries, queryResolveBuffer, 0);
+ encoder.copyBufferToBuffer(queryResolveBuffer, 0, readBuffer, 0, readBuffer.size);
+
+ // Rendering stuff unrelated should not affect results.
+ renderSomething(encoder);
+
+ encoder.resolveQuerySet(occlusionQuerySet, 0, kNumQueries, queryResolveBuffer, 0);
+ encoder.copyBufferToBuffer(queryResolveBuffer, 0, readBuffer2, 0, readBuffer2.size);
+ device.queue.submit([encoder.finish()]);
+ }
+
+ // Encode something else and draw again, then read the results
+ // They should not be affected.
+ {
+ const encoder = device.createCommandEncoder();
+ renderSomething(encoder);
+
+ encoder.resolveQuerySet(occlusionQuerySet, 0, kNumQueries, queryResolveBuffer, 0);
+ encoder.copyBufferToBuffer(queryResolveBuffer, 0, readBuffer3, 0, readBuffer3.size);
+ device.queue.submit([encoder.finish()]);
+ }
+
+ const results = await Promise.all([
+ t.readBufferAsBigUint64(readBuffer),
+ t.readBufferAsBigUint64(readBuffer2),
+ t.readBufferAsBigUint64(readBuffer3)]
+ );
+
+ results.forEach((result, r) => {
+ for (let i = 0; i < kNumQueries; ++i) {
+ const passed = !!result[i];
+ const expectPassed = !!(i % 2);
+ t.expect(
+ passed === expectPassed,
+ `result(${r}): queryIndex: ${i}, passed: ${passed}, expected: ${expectPassed}`
+ );
+ }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/dynamic_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/dynamic_state.spec.js
new file mode 100644
index 0000000000..639fbec824
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/dynamic_state.spec.js
@@ -0,0 +1,19 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests of the behavior of the viewport/scissor/blend/reference states.
+
+TODO:
+- {viewport, scissor rect, blend color, stencil reference}:
+ Test rendering result with {various values}.
+ - Set the state in different ways to make sure it gets the correct value in the end: {
+ - state unset (= default)
+ - state explicitly set once to {default value, another value}
+ - persistence: [set, draw, draw] (fn should differentiate from [set, draw] + [draw])
+ - overwriting: [set(1), draw, set(2), draw] (fn should differentiate from [set(1), set(2), draw, draw])
+ - overwriting: [set(1), set(2), draw] (fn should differentiate from [set(1), draw] but not [set(2), draw])
+ - }
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/state_tracking.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/state_tracking.spec.js
new file mode 100644
index 0000000000..9b3363f3ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/command_buffer/render/state_tracking.spec.js
@@ -0,0 +1,624 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Ensure state is set correctly. Tries to stress state caching (setting different states multiple
+times in different orders) for setIndexBuffer and setVertexBuffer.
+Equivalent tests for setBindGroup and setPipeline are in programmable/state_tracking.spec.ts.
+Equivalent tests for viewport/scissor/blend/reference are in render/dynamic_state.spec.ts
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest, TextureTestMixin } from '../../../../gpu_test.js';
+import { TexelView } from '../../../../util/texture/texel_view.js';
+
+class VertexAndIndexStateTrackingTest extends TextureTestMixin(GPUTest) {
+ GetRenderPipelineForTest(arrayStride) {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ struct Inputs {
+ @location(0) vertexPosition : f32,
+ @location(1) vertexColor : vec4<f32>,
+ };
+ struct Outputs {
+ @builtin(position) position : vec4<f32>,
+ @location(0) color : vec4<f32>,
+ };
+ @vertex
+ fn main(input : Inputs)-> Outputs {
+ var outputs : Outputs;
+ outputs.position =
+ vec4<f32>(input.vertexPosition, 0.5, 0.0, 1.0);
+ outputs.color = input.vertexColor;
+ return outputs;
+ }`
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ arrayStride,
+ attributes: [
+ {
+ format: 'float32',
+ offset: 0,
+ shaderLocation: 0
+ },
+ {
+ format: 'unorm8x4',
+ offset: 4,
+ shaderLocation: 1
+ }]
+
+ }]
+
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ struct Input {
+ @location(0) color : vec4<f32>
+ };
+ @fragment
+ fn main(input : Input) -> @location(0) vec4<f32> {
+ return input.color;
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+ }
+
+ kVertexAttributeSize = 8;
+}
+
+export const g = makeTestGroup(VertexAndIndexStateTrackingTest);
+
+g.test('set_index_buffer_without_changing_buffer').
+desc(
+ `
+ Test that setting index buffer states (index format, offset, size) multiple times in different
+ orders still keeps the correctness of each draw call.
+`
+).
+fn((t) => {
+ // Initialize the index buffer with 5 uint16 indices (0, 1, 2, 3, 4).
+ const indexBuffer = t.makeBufferWithContents(
+ new Uint16Array([0, 1, 2, 3, 4]),
+ GPUBufferUsage.INDEX
+ );
+
+ // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4)
+ // Note that the maximum index in the test is 0x10000.
+ const kVertexAttributesCount = 0x10000 + 1;
+ const vertexBuffer = t.device.createBuffer({
+ usage: GPUBufferUsage.VERTEX,
+ size: t.kVertexAttributeSize * kVertexAttributesCount,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(vertexBuffer);
+ const vertexAttributes = vertexBuffer.getMappedRange();
+ const kPositions = [-0.8, -0.4, 0.0, 0.4, 0.8, -0.4];
+ const kColors = [
+ new Uint8Array([255, 0, 0, 255]),
+ new Uint8Array([255, 255, 255, 255]),
+ new Uint8Array([0, 0, 255, 255]),
+ new Uint8Array([255, 0, 255, 255]),
+ new Uint8Array([0, 255, 255, 255]),
+ new Uint8Array([0, 255, 0, 255])];
+
+ // Set vertex attributes at index {0..4} in Uint16.
+ // Note that the vertex attribute at index 1 will not be used.
+ for (let i = 0; i < kPositions.length - 1; ++i) {
+ const baseOffset = t.kVertexAttributeSize * i;
+ const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1);
+ vertexPosition[0] = kPositions[i];
+ const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4);
+ vertexColor.set(kColors[i]);
+ }
+ // Set vertex attributes at index 0x10000.
+ const lastOffset = t.kVertexAttributeSize * (kVertexAttributesCount - 1);
+ const lastVertexPosition = new Float32Array(vertexAttributes, lastOffset, 1);
+ lastVertexPosition[0] = kPositions[kPositions.length - 1];
+ const lastVertexColor = new Uint8Array(vertexAttributes, lastOffset + 4, 4);
+ lastVertexColor.set(kColors[kColors.length - 1]);
+
+ vertexBuffer.unmap();
+
+ const renderPipeline = t.GetRenderPipelineForTest(t.kVertexAttributeSize);
+
+ const outputTextureSize = [kPositions.length - 1, 1, 1];
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: outputTextureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: [0, 0, 0, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(renderPipeline);
+ renderPass.setVertexBuffer(0, vertexBuffer);
+
+ // 1st draw: indexFormat = 'uint32', offset = 0, size = 4 (index value: 0x10000)
+ renderPass.setIndexBuffer(indexBuffer, 'uint32', 0, 4);
+ renderPass.drawIndexed(1);
+
+ // 2nd draw: indexFormat = 'uint16', offset = 0, size = 4 (index value: 0)
+ renderPass.setIndexBuffer(indexBuffer, 'uint16', 0, 4);
+ renderPass.drawIndexed(1);
+
+ // 3rd draw: indexFormat = 'uint16', offset = 4, size = 2 (index value: 2)
+ renderPass.setIndexBuffer(indexBuffer, 'uint16', 0, 2);
+ renderPass.setIndexBuffer(indexBuffer, 'uint16', 4, 2);
+ renderPass.drawIndexed(1);
+
+ // 4th draw: indexformat = 'uint16', offset = 6, size = 4 (index values: 3, 4)
+ renderPass.setIndexBuffer(indexBuffer, 'uint16', 6, 2);
+ renderPass.setIndexBuffer(indexBuffer, 'uint16', 6, 4);
+ renderPass.drawIndexed(2);
+
+ renderPass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: outputTexture },
+ TexelView.fromTexelsAsBytes('rgba8unorm', (coord) =>
+ coord.x === 1 ? kColors[kPositions.length - 1] : kColors[coord.x]
+ ),
+ outputTextureSize
+ );
+});
+
+g.test('set_vertex_buffer_without_changing_buffer').
+desc(
+ `
+ Test that setting vertex buffer states (offset, size) multiple times in different orders still
+ keeps the correctness of each draw call.
+ - Tries several different sequences of setVertexBuffer+draw commands, each of which draws vertices
+ in all 4 output pixels, and check they were drawn correctly.
+`
+).
+fn((t) => {
+ const kPositions = [-0.875, -0.625, -0.375, -0.125, 0.125, 0.375, 0.625, 0.875];
+ const kColors = [
+ new Uint8Array([255, 0, 0, 255]),
+ new Uint8Array([0, 255, 0, 255]),
+ new Uint8Array([0, 0, 255, 255]),
+ new Uint8Array([51, 0, 0, 255]),
+ new Uint8Array([0, 51, 0, 255]),
+ new Uint8Array([0, 0, 51, 255]),
+ new Uint8Array([255, 0, 255, 255]),
+ new Uint8Array([255, 255, 0, 255])];
+
+
+ // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4)
+ const kVertexAttributesCount = 8;
+ const vertexBuffer = t.device.createBuffer({
+ usage: GPUBufferUsage.VERTEX,
+ size: t.kVertexAttributeSize * kVertexAttributesCount,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(vertexBuffer);
+ const vertexAttributes = vertexBuffer.getMappedRange();
+ for (let i = 0; i < kPositions.length; ++i) {
+ const baseOffset = t.kVertexAttributeSize * i;
+ const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1);
+ vertexPosition[0] = kPositions[i];
+ const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4);
+ vertexColor.set(kColors[i]);
+ }
+
+ vertexBuffer.unmap();
+
+ const renderPipeline = t.GetRenderPipelineForTest(t.kVertexAttributeSize);
+
+ const outputTextureSize = [kPositions.length, 1, 1];
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: outputTextureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: [0, 0, 0, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(renderPipeline);
+
+ // Change 'size' in setVertexBuffer()
+ renderPass.setVertexBuffer(0, vertexBuffer, 0, t.kVertexAttributeSize);
+ renderPass.setVertexBuffer(0, vertexBuffer, 0, t.kVertexAttributeSize * 2);
+ renderPass.draw(2);
+
+ // Change 'offset' in setVertexBuffer()
+ renderPass.setVertexBuffer(
+ 0,
+ vertexBuffer,
+ t.kVertexAttributeSize * 2,
+ t.kVertexAttributeSize * 2
+ );
+ renderPass.draw(2);
+
+ // Change 'size' again in setVertexBuffer()
+ renderPass.setVertexBuffer(
+ 0,
+ vertexBuffer,
+ t.kVertexAttributeSize * 4,
+ t.kVertexAttributeSize * 2
+ );
+ renderPass.setVertexBuffer(
+ 0,
+ vertexBuffer,
+ t.kVertexAttributeSize * 4,
+ t.kVertexAttributeSize * 4
+ );
+ renderPass.draw(4);
+
+ renderPass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: outputTexture },
+ TexelView.fromTexelsAsBytes('rgba8unorm', (coord) => kColors[coord.x]),
+ outputTextureSize
+ );
+});
+
+g.test('change_pipeline_before_and_after_vertex_buffer').
+desc(
+ `
+ Test that changing the pipeline {before,after} the vertex buffers still keeps the correctness of
+ each draw call (In D3D12, the vertex buffer stride is part of SetVertexBuffer instead of the
+ pipeline.)
+`
+).
+fn((t) => {
+ const kPositions = [-0.8, -0.4, 0.0, 0.4, 0.8, 0.9];
+ const kColors = [
+ new Uint8Array([255, 0, 0, 255]),
+ new Uint8Array([255, 255, 255, 255]),
+ new Uint8Array([0, 255, 0, 255]),
+ new Uint8Array([0, 0, 255, 255]),
+ new Uint8Array([255, 0, 255, 255]),
+ new Uint8Array([0, 255, 255, 255])];
+
+
+ // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4)
+ const vertexBuffer = t.device.createBuffer({
+ usage: GPUBufferUsage.VERTEX,
+ size: t.kVertexAttributeSize * kPositions.length,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(vertexBuffer);
+ // Note that kPositions[1], kColors[1], kPositions[5] and kColors[5] are not used.
+ const vertexAttributes = vertexBuffer.getMappedRange();
+ for (let i = 0; i < kPositions.length; ++i) {
+ const baseOffset = t.kVertexAttributeSize * i;
+ const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1);
+ vertexPosition[0] = kPositions[i];
+ const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4);
+ vertexColor.set(kColors[i]);
+ }
+ vertexBuffer.unmap();
+
+ // Create two render pipelines with different vertex attribute strides
+ const renderPipeline1 = t.GetRenderPipelineForTest(t.kVertexAttributeSize);
+ const renderPipeline2 = t.GetRenderPipelineForTest(t.kVertexAttributeSize * 2);
+
+ const kPointsCount = kPositions.length - 1;
+ const outputTextureSize = [kPointsCount, 1, 1];
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: outputTextureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: [0, 0, 0, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ // Update render pipeline before setVertexBuffer. The applied vertex attribute stride should be
+ // 2 * kVertexAttributeSize.
+ renderPass.setPipeline(renderPipeline1);
+ renderPass.setPipeline(renderPipeline2);
+ renderPass.setVertexBuffer(0, vertexBuffer);
+ renderPass.draw(2);
+
+ // Update render pipeline after setVertexBuffer. The applied vertex attribute stride should be
+ // kVertexAttributeSize.
+ renderPass.setVertexBuffer(0, vertexBuffer, 3 * t.kVertexAttributeSize);
+ renderPass.setPipeline(renderPipeline1);
+ renderPass.draw(2);
+
+ renderPass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: outputTexture },
+ TexelView.fromTexelsAsBytes('rgba8unorm', (coord) =>
+ coord.x === 1 ? new Uint8Array([0, 0, 0, 255]) : kColors[coord.x]
+ ),
+ outputTextureSize
+ );
+});
+
+g.test('set_vertex_buffer_but_not_used_in_draw').
+desc(
+ `
+ Test that drawing after having set vertex buffer slots not used by the pipeline works correctly.
+ - In the test there are 2 draw calls in the render pass. The first draw call uses 2 vertex buffers
+ (position and color), and the second draw call only uses 1 vertex buffer (for color, the vertex
+ position is defined as constant values in the vertex shader). The test verifies if both of these
+ two draw calls work correctly.
+ `
+).
+fn((t) => {
+ const kPositions = new Float32Array([-0.75, -0.25]);
+ const kColors = new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255]);
+
+ // Initialize the vertex buffers with required vertex attributes (position: f32, color: f32x4)
+ const kAttributeStride = 4;
+ const positionBuffer = t.makeBufferWithContents(kPositions, GPUBufferUsage.VERTEX);
+ const colorBuffer = t.makeBufferWithContents(kColors, GPUBufferUsage.VERTEX);
+
+ const fragmentState = {
+ module: t.device.createShaderModule({
+ code: `
+ struct Input {
+ @location(0) color : vec4<f32>
+ };
+ @fragment
+ fn main(input : Input) -> @location(0) vec4<f32> {
+ return input.color;
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ };
+
+ // Create renderPipeline1 that uses both positionBuffer and colorBuffer.
+ const renderPipeline1 = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ struct Inputs {
+ @location(0) vertexColor : vec4<f32>,
+ @location(1) vertexPosition : f32,
+ };
+ struct Outputs {
+ @builtin(position) position : vec4<f32>,
+ @location(0) color : vec4<f32>,
+ };
+ @vertex
+ fn main(input : Inputs)-> Outputs {
+ var outputs : Outputs;
+ outputs.position =
+ vec4<f32>(input.vertexPosition, 0.5, 0.0, 1.0);
+ outputs.color = input.vertexColor;
+ return outputs;
+ }`
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ arrayStride: kAttributeStride,
+ attributes: [
+ {
+ format: 'unorm8x4',
+ offset: 0,
+ shaderLocation: 0
+ }]
+
+ },
+ {
+ arrayStride: kAttributeStride,
+ attributes: [
+ {
+ format: 'float32',
+ offset: 0,
+ shaderLocation: 1
+ }]
+
+ }]
+
+ },
+ fragment: fragmentState,
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+
+ const renderPipeline2 = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ struct Inputs {
+ @builtin(vertex_index) vertexIndex : u32,
+ @location(0) vertexColor : vec4<f32>,
+ };
+ struct Outputs {
+ @builtin(position) position : vec4<f32>,
+ @location(0) color : vec4<f32>,
+ };
+ @vertex
+ fn main(input : Inputs)-> Outputs {
+ var kPositions = array<f32, 2> (0.25, 0.75);
+ var outputs : Outputs;
+ outputs.position =
+ vec4(kPositions[input.vertexIndex], 0.5, 0.0, 1.0);
+ outputs.color = input.vertexColor;
+ return outputs;
+ }`
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ arrayStride: kAttributeStride,
+ attributes: [
+ {
+ format: 'unorm8x4',
+ offset: 0,
+ shaderLocation: 0
+ }]
+
+ }]
+
+ },
+ fragment: fragmentState,
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+
+ const kPointsCount = 4;
+ const outputTextureSize = [kPointsCount, 1, 1];
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [kPointsCount, 1, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: [0, 0, 0, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ renderPass.setVertexBuffer(0, colorBuffer);
+ renderPass.setVertexBuffer(1, positionBuffer);
+ renderPass.setPipeline(renderPipeline1);
+ renderPass.draw(2);
+
+ renderPass.setPipeline(renderPipeline2);
+ renderPass.draw(2);
+
+ renderPass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ const kExpectedColors = [
+ kColors.subarray(0, 4),
+ kColors.subarray(4),
+ kColors.subarray(0, 4),
+ kColors.subarray(4)];
+
+
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: outputTexture },
+ TexelView.fromTexelsAsBytes('rgba8unorm', (coord) => kExpectedColors[coord.x]),
+ outputTextureSize
+ );
+});
+
+g.test('set_index_buffer_before_non_indexed_draw').
+desc(
+ `
+ Test that setting / not setting the index buffer does not impact a non-indexed draw.
+ `
+).
+fn((t) => {
+ const kPositions = [-0.75, -0.25, 0.25, 0.75];
+ const kColors = [
+ new Uint8Array([255, 0, 0, 255]),
+ new Uint8Array([0, 255, 0, 255]),
+ new Uint8Array([0, 0, 255, 255]),
+ new Uint8Array([255, 0, 255, 255])];
+
+
+ // Initialize the vertex buffer with required vertex attributes (position: f32, color: f32x4)
+ const vertexBuffer = t.device.createBuffer({
+ usage: GPUBufferUsage.VERTEX,
+ size: t.kVertexAttributeSize * kPositions.length,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(vertexBuffer);
+ const vertexAttributes = vertexBuffer.getMappedRange();
+ for (let i = 0; i < kPositions.length; ++i) {
+ const baseOffset = t.kVertexAttributeSize * i;
+ const vertexPosition = new Float32Array(vertexAttributes, baseOffset, 1);
+ vertexPosition[0] = kPositions[i];
+ const vertexColor = new Uint8Array(vertexAttributes, baseOffset + 4, 4);
+ vertexColor.set(kColors[i]);
+ }
+ vertexBuffer.unmap();
+
+ // Initialize the index buffer with 2 uint16 indices (2, 3).
+ const indexBuffer = t.makeBufferWithContents(new Uint16Array([2, 3]), GPUBufferUsage.INDEX);
+
+ const renderPipeline = t.GetRenderPipelineForTest(t.kVertexAttributeSize);
+
+ const kPointsCount = 4;
+ const outputTextureSize = [kPointsCount, 1, 1];
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [kPointsCount, 1, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: [0, 0, 0, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ // The first draw call is an indexed one (the third and fourth color are involved)
+ renderPass.setVertexBuffer(0, vertexBuffer);
+ renderPass.setIndexBuffer(indexBuffer, 'uint16');
+ renderPass.setPipeline(renderPipeline);
+ renderPass.drawIndexed(2);
+
+ // The second draw call is a non-indexed one (the first and second color are involved)
+ renderPass.draw(2);
+
+ renderPass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: outputTexture },
+ TexelView.fromTexelsAsBytes('rgba8unorm', (coord) => kColors[coord.x]),
+ outputTextureSize
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute/basic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute/basic.spec.js
new file mode 100644
index 0000000000..2875f8a0ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute/basic.spec.js
@@ -0,0 +1,162 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Basic command buffer compute tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { checkElementsEqualGenerated } from '../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('memcpy').fn((t) => {
+ const data = new Uint32Array([0x01020304]);
+
+ const src = t.makeBufferWithContents(data, GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE);
+
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ struct Data {
+ value : u32
+ };
+
+ @group(0) @binding(0) var<storage, read> src : Data;
+ @group(0) @binding(1) var<storage, read_write> dst : Data;
+
+ @compute @workgroup_size(1) fn main() {
+ dst.value = src.value;
+ return;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const bg = t.device.createBindGroup({
+ entries: [
+ { binding: 0, resource: { buffer: src, offset: 0, size: 4 } },
+ { binding: 1, resource: { buffer: dst, offset: 0, size: 4 } }],
+
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(dst, data);
+});
+
+g.test('large_dispatch').
+desc(`Test reasonably-sized large dispatches (see also: stress tests).`).
+params((u) =>
+u
+// Reasonably-sized powers of two, and some stranger larger sizes.
+.combine('dispatchSize', [256, 2048, 315, 628, 2179, 'maximum'])
+// Test some reasonable workgroup sizes.
+.beginSubcases()
+// 0 == x axis; 1 == y axis; 2 == z axis.
+.combine('largeDimension', [0, 1, 2]).
+expand('workgroupSize', () => [1, 2, 8, 32, 'maximum'])
+).
+fn((t) => {
+ // The output storage buffer is filled with this value.
+ const val = 0x01020304;
+ const badVal = 0xbaadf00d;
+
+ const kMaxComputeWorkgroupSize = [
+ t.device.limits.maxComputeWorkgroupSizeX,
+ t.device.limits.maxComputeWorkgroupSizeY,
+ t.device.limits.maxComputeWorkgroupSizeZ];
+
+
+ const wgSize =
+ t.params.workgroupSize === 'maximum' ?
+ kMaxComputeWorkgroupSize[t.params.largeDimension] :
+ t.params.workgroupSize;
+ const dispatchSize =
+ t.params.dispatchSize === 'maximum' ?
+ t.device.limits.maxComputeWorkgroupsPerDimension :
+ t.params.dispatchSize;
+ const bufferLength = dispatchSize * wgSize;
+ const bufferByteSize = Uint32Array.BYTES_PER_ELEMENT * bufferLength;
+ const dst = t.device.createBuffer({
+ size: bufferByteSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+
+ // Only use one large dimension and workgroup size in the dispatch
+ // call to keep the size of the test reasonable.
+ const dims = [1, 1, 1];
+ dims[t.params.largeDimension] = dispatchSize;
+ const wgSizes = [1, 1, 1];
+ wgSizes[t.params.largeDimension] = wgSize;
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ struct OutputBuffer {
+ value : array<u32>
+ };
+
+ @group(0) @binding(0) var<storage, read_write> dst : OutputBuffer;
+
+ @compute @workgroup_size(${wgSizes[0]}, ${wgSizes[1]}, ${wgSizes[2]})
+ fn main(
+ @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
+ ) {
+ var xExtent : u32 = ${dims[0]}u * ${wgSizes[0]}u;
+ var yExtent : u32 = ${dims[1]}u * ${wgSizes[1]}u;
+ var zExtent : u32 = ${dims[2]}u * ${wgSizes[2]}u;
+ var index : u32 = (
+ GlobalInvocationID.z * xExtent * yExtent +
+ GlobalInvocationID.y * xExtent +
+ GlobalInvocationID.x);
+ var val : u32 = ${val}u;
+ // Trivial error checking in the indexing and invocation.
+ if (GlobalInvocationID.x > xExtent ||
+ GlobalInvocationID.y > yExtent ||
+ GlobalInvocationID.z > zExtent) {
+ val = ${badVal}u;
+ }
+ dst.value[index] = val;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const bg = t.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer: dst, offset: 0, size: bufferByteSize } }],
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(dims[0], dims[1], dims[2]);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesPassCheck(dst, (a) => checkElementsEqualGenerated(a, (_i) => val), {
+ type: Uint32Array,
+ typedLength: bufferLength
+ });
+
+ dst.destroy();
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/entry_point_name.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/entry_point_name.spec.js
new file mode 100644
index 0000000000..2e675a84d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/entry_point_name.spec.js
@@ -0,0 +1,12 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+TODO:
+- Test some weird but valid values for entry point name (both module and pipeline creation
+ should succeed).
+- Test using each of many entry points in the module (should succeed).
+- Test using an entry point with the wrong stage (should fail).
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/overrides.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/overrides.spec.js
new file mode 100644
index 0000000000..35d8c437fd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/compute_pipeline/overrides.spec.js
@@ -0,0 +1,503 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Compute pipeline using overridable constants test.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+class F extends GPUTest {
+ async ExpectShaderOutputWithConstants(
+ isAsync,
+ expected,
+ constants,
+ code)
+ {
+ const dst = this.device.createBuffer({
+ size: expected.byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: this.device.createShaderModule({
+ code
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ const promise = isAsync ?
+ this.device.createComputePipelineAsync(descriptor) :
+ Promise.resolve(this.device.createComputePipeline(descriptor));
+
+ const pipeline = await promise;
+ const bindGroup = this.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer: dst, offset: 0, size: expected.byteLength } }],
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ this.expectGPUBufferValuesEqual(dst, expected);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('basic').
+desc(
+ `Test that either correct constants override values or default values when no constants override value are provided at pipeline creation time are used as the output to the storage buffer.`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn(async (t) => {
+ const count = 11;
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ new Uint32Array(range(count, (i) => i)),
+ {
+ c0: 0,
+ c1: 1,
+ c2: 2,
+ c3: 3,
+ // c4 is using default value
+ c5: 5,
+ c6: 6,
+ // c7 is using default value
+ c8: 8,
+ c9: 9
+ // c10 is using default value
+ },
+ `
+ override c0: bool; // type: bool
+ override c1: bool = false; // default override
+ override c2: f32; // type: float32
+ override c3: f32 = 0.0; // default override
+ override c4: f32 = 4.0; // default
+ override c5: i32; // type: int32
+ override c6: i32 = 0; // default override
+ override c7: i32 = 7; // default
+ override c8: u32; // type: uint32
+ override c9: u32 = 0u; // default override
+ override c10: u32 = 10u; // default
+
+ struct Buf {
+ data : array<u32, ${count}>
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+ @compute @workgroup_size(1) fn main() {
+ buf.data[0] = u32(c0);
+ buf.data[1] = u32(c1);
+ buf.data[2] = u32(c2);
+ buf.data[3] = u32(c3);
+ buf.data[4] = u32(c4);
+ buf.data[5] = u32(c5);
+ buf.data[6] = u32(c6);
+ buf.data[7] = u32(c7);
+ buf.data[8] = u32(c8);
+ buf.data[9] = u32(c9);
+ buf.data[10] = u32(c10);
+ }
+ `
+ );
+});
+
+g.test('numeric_id').
+desc(
+ `Test that correct values are used as output to the storage buffer for constants specified with numeric id instead of their names.`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn(async (t) => {
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ new Uint32Array([1, 2, 3]),
+ {
+ 1001: 1,
+ 1: 2
+ // 1003 is using default value
+ },
+ `
+ @id(1001) override c1: u32; // some big numeric id
+ @id(1) override c2: u32 = 0u; // id == 1 might collide with some generated constant id
+ @id(1003) override c3: u32 = 3u; // default
+
+ struct Buf {
+ data : array<u32, 3>
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+ @compute @workgroup_size(1) fn main() {
+ buf.data[0] = c1;
+ buf.data[1] = c2;
+ buf.data[2] = c3;
+ }
+ `
+ );
+});
+
+g.test('precision').
+desc(
+ `Test that float number precision is preserved for constants as they are used for compute shader output of the storage buffer.`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn(async (t) => {
+ const c1 = 3.14159;
+ const c2 = 3.141592653589793;
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ // These values will get rounded to f32 and createComputePipeline, so the values coming out from the shader won't be the exact same one as shown here.
+ new Float32Array([c1, c2]),
+ {
+ c1,
+ c2
+ },
+ `
+ override c1: f32;
+ override c2: f32;
+
+ struct Buf {
+ data : array<f32, 2>
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+ @compute @workgroup_size(1) fn main() {
+ buf.data[0] = c1;
+ buf.data[1] = c2;
+ }
+ `
+ );
+});
+
+g.test('workgroup_size').
+desc(
+ `Test that constants can be used as workgroup size correctly, the compute shader should write the max local invocation id to the storage buffer which is equal to the workgroup size dimension given by the constant.`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combine('type', ['u32', 'i32']).
+combine('size', [3, 16, 64]).
+combine('v', ['x', 'y', 'z'])
+).
+fn(async (t) => {
+ const { isAsync, type, size, v } = t.params;
+ const workgroup_size_str = v === 'x' ? 'd' : v === 'y' ? '1, d' : '1, 1, d';
+ await t.ExpectShaderOutputWithConstants(
+ isAsync,
+ new Uint32Array([size]),
+ {
+ d: size
+ },
+ `
+ override d: ${type};
+
+ struct Buf {
+ data : array<u32, 1>
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+ @compute @workgroup_size(${workgroup_size_str}) fn main(
+ @builtin(local_invocation_id) local_invocation_id : vec3<u32>
+ ) {
+ if (local_invocation_id.${v} >= u32(d - 1)) {
+ buf.data[0] = local_invocation_id.${v} + 1;
+ }
+ }
+ `
+ );
+});
+
+g.test('shared_shader_module').
+desc(
+ `Test that when the same shader module is shared by different pipelines, the correct constant values are used as output to the storage buffer. The constant value should not affect other pipeline sharing the same shader module.`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn(async (t) => {
+ const module = t.device.createShaderModule({
+ code: `
+ override a: u32;
+
+ struct Buf {
+ data : array<u32, 1>
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+ @compute @workgroup_size(1) fn main() {
+ buf.data[0] = a;
+ }`
+ });
+
+ const expects = [new Uint32Array([1]), new Uint32Array([2])];
+ const buffers = [
+ t.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ }),
+ t.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ })];
+
+
+ const descriptors = [
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main',
+ constants: {
+ a: 1
+ }
+ }
+ },
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main',
+ constants: {
+ a: 2
+ }
+ }
+ }];
+
+
+ const promises = t.params.isAsync ?
+ Promise.all([
+ t.device.createComputePipelineAsync(descriptors[0]),
+ t.device.createComputePipelineAsync(descriptors[1])]
+ ) :
+ Promise.resolve([
+ t.device.createComputePipeline(descriptors[0]),
+ t.device.createComputePipeline(descriptors[1])]
+ );
+
+ const pipelines = await promises;
+ const bindGroups = [
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: buffers[0], offset: 0, size: Uint32Array.BYTES_PER_ELEMENT }
+ }],
+
+ layout: pipelines[0].getBindGroupLayout(0)
+ }),
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: buffers[1], offset: 0, size: Uint32Array.BYTES_PER_ELEMENT }
+ }],
+
+ layout: pipelines[1].getBindGroupLayout(0)
+ })];
+
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipelines[0]);
+ pass.setBindGroup(0, bindGroups[0]);
+ pass.dispatchWorkgroups(1);
+ pass.setPipeline(pipelines[1]);
+ pass.setBindGroup(0, bindGroups[1]);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(buffers[0], expects[0]);
+ t.expectGPUBufferValuesEqual(buffers[1], expects[1]);
+});
+
+g.test('multi_entry_points').
+desc(
+ `Test that constants used for different entry points are used correctly as output to the storage buffer. They should have no impact for pipeline using entry points that doesn't reference them.`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn(async (t) => {
+ const module = t.device.createShaderModule({
+ code: `
+ override c1: u32;
+ override c2: u32;
+ override c3: u32;
+
+ struct Buf {
+ data : array<u32, 1>
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buf : Buf;
+
+ @compute @workgroup_size(1) fn main1() {
+ buf.data[0] = c1;
+ }
+
+ @compute @workgroup_size(1) fn main2() {
+ buf.data[0] = c2;
+ }
+
+ @compute @workgroup_size(c3) fn main3() {
+ buf.data[0] = 3u;
+ }`
+ });
+
+ const expects = [
+ new Uint32Array([1]),
+ new Uint32Array([2]),
+ new Uint32Array([3]),
+ new Uint32Array([4])];
+
+
+ const buffers = [
+ t.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ }),
+ t.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ }),
+ t.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ }),
+ t.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ })];
+
+
+ const descriptors = [
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main1',
+ constants: {
+ c1: 1
+ }
+ }
+ },
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main2',
+ constants: {
+ c2: 2
+ }
+ }
+ },
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main3',
+ constants: {
+ // c3 is used as workgroup size
+ c3: 1
+ }
+ }
+ },
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main1',
+ constants: {
+ // assign a different value to c1
+ c1: 4
+ }
+ }
+ }];
+
+
+ const promises = t.params.isAsync ?
+ Promise.all([
+ t.device.createComputePipelineAsync(descriptors[0]),
+ t.device.createComputePipelineAsync(descriptors[1]),
+ t.device.createComputePipelineAsync(descriptors[2]),
+ t.device.createComputePipelineAsync(descriptors[3])]
+ ) :
+ Promise.resolve([
+ t.device.createComputePipeline(descriptors[0]),
+ t.device.createComputePipeline(descriptors[1]),
+ t.device.createComputePipeline(descriptors[2]),
+ t.device.createComputePipeline(descriptors[3])]
+ );
+
+ const pipelines = await promises;
+ const bindGroups = [
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: buffers[0], offset: 0, size: Uint32Array.BYTES_PER_ELEMENT }
+ }],
+
+ layout: pipelines[0].getBindGroupLayout(0)
+ }),
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: buffers[1], offset: 0, size: Uint32Array.BYTES_PER_ELEMENT }
+ }],
+
+ layout: pipelines[1].getBindGroupLayout(0)
+ }),
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: buffers[2], offset: 0, size: Uint32Array.BYTES_PER_ELEMENT }
+ }],
+
+ layout: pipelines[2].getBindGroupLayout(0)
+ }),
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: buffers[3], offset: 0, size: Uint32Array.BYTES_PER_ELEMENT }
+ }],
+
+ layout: pipelines[3].getBindGroupLayout(0)
+ })];
+
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipelines[0]);
+ pass.setBindGroup(0, bindGroups[0]);
+ pass.dispatchWorkgroups(1);
+ pass.setPipeline(pipelines[1]);
+ pass.setBindGroup(0, bindGroups[1]);
+ pass.dispatchWorkgroups(1);
+ pass.setPipeline(pipelines[2]);
+ pass.setBindGroup(0, bindGroups[2]);
+ pass.dispatchWorkgroups(1);
+ pass.setPipeline(pipelines[3]);
+ pass.setBindGroup(0, bindGroups[3]);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(buffers[0], expects[0]);
+ t.expectGPUBufferValuesEqual(buffers[1], expects[1]);
+ t.expectGPUBufferValuesEqual(buffers[2], expects[2]);
+ t.expectGPUBufferValuesEqual(buffers[3], expects[3]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/device/lost.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/device/lost.spec.js
new file mode 100644
index 0000000000..e86b009d23
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/device/lost.spec.js
@@ -0,0 +1,92 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for GPUDevice.lost.
+`;import { Fixture } from '../../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { attemptGarbageCollection } from '../../../../common/util/collect_garbage.js';
+import { getGPU } from '../../../../common/util/navigator_gpu.js';
+import {
+ assert,
+ assertNotSettledWithinTime,
+ raceWithRejectOnTimeout } from
+'../../../../common/util/util.js';
+
+class DeviceLostTests extends Fixture {
+ // Default timeout for waiting for device lost is 2 seconds.
+ kDeviceLostTimeoutMS = 2000;
+
+ getDeviceLostWithTimeout(lost) {
+ return raceWithRejectOnTimeout(lost, this.kDeviceLostTimeoutMS, 'device was not lost');
+ }
+
+ expectDeviceDestroyed(device) {
+ this.eventualAsyncExpectation(async (niceStack) => {
+ try {
+ const lost = await this.getDeviceLostWithTimeout(device.lost);
+ this.expect(lost.reason === 'destroyed', 'device was lost from destroy');
+ } catch (ex) {
+ niceStack.message = 'device was not lost';
+ this.rec.expectationFailed(niceStack);
+ }
+ });
+ }
+}
+
+export const g = makeTestGroup(DeviceLostTests);
+
+g.test('not_lost_on_gc').
+desc(
+ `'lost' is never resolved by GPUDevice being garbage collected (with attemptGarbageCollection).`
+).
+fn(async (t) => {
+ // Wraps a lost promise object creation in a function scope so that the device has the best
+ // chance of being gone and ready for GC before trying to resolve the lost promise.
+ const { lost } = await (async () => {
+ const adapter = await getGPU(t.rec).requestAdapter();
+ assert(adapter !== null);
+ const lost = (await adapter.requestDevice()).lost;
+ return { lost };
+ })();
+ await assertNotSettledWithinTime(lost, t.kDeviceLostTimeoutMS, 'device was unexpectedly lost');
+
+ await attemptGarbageCollection();
+});
+
+g.test('lost_on_destroy').
+desc(`'lost' is resolved, with reason='destroyed', on GPUDevice.destroy().`).
+fn(async (t) => {
+ const adapter = await getGPU(t.rec).requestAdapter();
+ assert(adapter !== null);
+ const device = await adapter.requestDevice();
+ t.expectDeviceDestroyed(device);
+ device.destroy();
+});
+
+g.test('same_object').
+desc(`'lost' provides the same Promise and GPUDeviceLostInfo objects each time it's accessed.`).
+fn(async (t) => {
+ const adapter = await getGPU(t.rec).requestAdapter();
+ assert(adapter !== null);
+ const device = await adapter.requestDevice();
+
+ // The promises should be the same promise object.
+ const lostPromise1 = device.lost;
+ const lostPromise2 = device.lost;
+ t.expect(lostPromise1 === lostPromise2);
+
+ // Promise object should still be the same after destroy.
+ device.destroy();
+ const lostPromise3 = device.lost;
+ t.expect(lostPromise1 === lostPromise3);
+
+ // The results should also be the same result object.
+ const lost1 = await t.getDeviceLostWithTimeout(lostPromise1);
+ const lost2 = await t.getDeviceLostWithTimeout(lostPromise2);
+ const lost3 = await t.getDeviceLostWithTimeout(lostPromise3);
+ // Promise object should still be the same after we've been notified about device loss.
+ const lostPromise4 = device.lost;
+ t.expect(lostPromise1 === lostPromise4);
+ const lost4 = await t.getDeviceLostWithTimeout(lostPromise4);
+ t.expect(lost1 === lost2 && lost2 === lost3 && lost3 === lost4);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/labels.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/labels.spec.js
new file mode 100644
index 0000000000..7da653f5ac
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/labels.spec.js
@@ -0,0 +1,280 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for object labels.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { keysOf } from '../../../common/util/data_tables.js';
+import { getGPU } from '../../../common/util/navigator_gpu.js';
+import { GPUTest } from '../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+
+const kTestFunctions = {
+ createBuffer: (t, label) => {
+ const buffer = t.device.createBuffer({ size: 16, usage: GPUBufferUsage.COPY_DST, label });
+ t.expect(buffer.label === label);
+ buffer.destroy();
+ t.expect(buffer.label === label);
+ },
+
+ requestDevice: async (t, label) => {
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ t.expect(!!adapter);
+ const device = await adapter.requestDevice({ label });
+ t.expect(!!device);
+ t.expect(device.label === label);
+ device.destroy();
+ t.expect(device.label === label);
+ },
+
+ createTexture: (t, label) => {
+ const texture = t.device.createTexture({
+ label,
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.expect(texture.label === label);
+ texture.destroy();
+ t.expect(texture.label === label);
+ },
+
+ createSampler: (t, label) => {
+ const sampler = t.device.createSampler({ label });
+ t.expect(sampler.label === label);
+ },
+
+ createBindGroupLayout: (t, label) => {
+ const bindGroupLayout = t.device.createBindGroupLayout({ label, entries: [] });
+ t.expect(bindGroupLayout.label === label);
+ },
+
+ createPipelineLayout: (t, label) => {
+ const pipelineLayout = t.device.createPipelineLayout({ label, bindGroupLayouts: [] });
+ t.expect(pipelineLayout.label === label);
+ },
+
+ createBindGroup: (t, label) => {
+ const layout = t.device.createBindGroupLayout({ entries: [] });
+ const bindGroup = t.device.createBindGroup({ label, layout, entries: [] });
+ t.expect(bindGroup.label === label);
+ },
+
+ createShaderModule: (t, label) => {
+ const shaderModule = t.device.createShaderModule({
+ label,
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0, 0, 0, 1);
+ }
+ `
+ });
+ t.expect(shaderModule.label === label);
+ },
+
+ createComputePipeline: (t, label) => {
+ const module = t.device.createShaderModule({
+ code: `
+ @compute @workgroup_size(1u) fn foo() {}
+ `
+ });
+ const computePipeline = t.device.createComputePipeline({
+ label,
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'foo'
+ }
+ });
+ t.expect(computePipeline.label === label);
+ },
+
+ createRenderPipeline: (t, label) => {
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn foo() -> @builtin(position) vec4f {
+ return vec4f(0, 0, 0, 1);
+ }
+ `
+ });
+ const renderPipeline = t.device.createRenderPipeline({
+ label,
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'foo'
+ }
+ });
+ t.expect(renderPipeline.label === label);
+ },
+
+ createComputePipelineAsync: async (t, label) => {
+ const module = t.device.createShaderModule({
+ code: `
+ @compute @workgroup_size(1u) fn foo() {}
+ `
+ });
+ const computePipeline = await t.device.createComputePipelineAsync({
+ label,
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'foo'
+ }
+ });
+ t.expect(computePipeline.label === label);
+ },
+
+ createRenderPipelineAsync: async (t, label) => {
+ const module = t.device.createShaderModule({
+ label,
+ code: `
+ @vertex fn foo() -> @builtin(position) vec4f {
+ return vec4f(0, 0, 0, 1);
+ }
+ `
+ });
+ const renderPipeline = await t.device.createRenderPipelineAsync({
+ label,
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'foo'
+ }
+ });
+ t.expect(renderPipeline.label === label);
+ },
+
+ createCommandEncoder: (t, label) => {
+ const encoder = t.device.createCommandEncoder({ label });
+ t.expect(encoder.label === label);
+ },
+
+ createRenderBundleEncoder: (t, label) => {
+ const encoder = t.device.createRenderBundleEncoder({
+ label,
+ colorFormats: ['rgba8unorm']
+ });
+ t.expect(encoder.label === label);
+ },
+
+ createQuerySet: (t, label) => {
+ const querySet = t.device.createQuerySet({
+ label,
+ type: 'occlusion',
+ count: 1
+ });
+ t.expect(querySet.label === label);
+ querySet.destroy();
+ t.expect(querySet.label === label);
+ },
+
+ beginRenderPass: (t, label) => {
+ const texture = t.device.createTexture({
+ label,
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const label2 = `${label}-2`;
+ const encoder = t.device.createCommandEncoder();
+ encoder.label = label2;
+ const renderPass = encoder.beginRenderPass({
+ label,
+ colorAttachments: [{ view: texture.createView(), loadOp: 'clear', storeOp: 'store' }]
+ });
+ t.expect(renderPass.label === label);
+ renderPass.end();
+ t.expect(renderPass.label === label);
+ encoder.finish();
+ t.expect(renderPass.label === label);
+ t.expect(encoder.label === label2);
+ texture.destroy();
+ },
+
+ beginComputePass: (t, label) => {
+ const label2 = `${label}-2`;
+ const encoder = t.device.createCommandEncoder();
+ encoder.label = label2;
+ const computePass = encoder.beginComputePass({ label });
+ t.expect(computePass.label === label);
+ computePass.end();
+ t.expect(computePass.label === label);
+ encoder.finish();
+ t.expect(computePass.label === label);
+ t.expect(encoder.label === label2);
+ },
+
+ finish: (t, label) => {
+ const encoder = t.device.createCommandEncoder();
+ const commandBuffer = encoder.finish({ label });
+ t.expect(commandBuffer.label === label);
+ },
+
+ createView: (t, label) => {
+ const texture = t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const view = texture.createView({ label });
+ t.expect(view.label === label);
+ texture.destroy();
+ t.expect(view.label === label);
+ }
+};
+
+g.test('object_has_descriptor_label').
+desc(
+ `
+ For every create function, the descriptor.label is carried over to the object.label.
+
+ TODO: test importExternalTexture
+ TODO: make a best effort and generating an error that is likely to use label. There's nothing to check for
+ but it may surface bugs related to unusual labels.
+ `
+).
+params((u) =>
+u.
+combine('name', keysOf(kTestFunctions)).
+beginSubcases().
+combine('label', ['label', '\0', 'null\0in\0label', '🌞👆'])
+).
+fn(async (t) => {
+ const { name, label } = t.params;
+ const result = kTestFunctions[name](t, label);
+ if (result instanceof Promise) {
+ await result;
+ }
+});
+
+g.test('wrappers_do_not_share_labels').
+desc('test that different wrapper objects for the same GPU object do not share labels').
+fn((t) => {
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var<uniform> pos: vec4f;
+ @vertex fn main() -> @builtin(position) vec4f {
+ return pos;
+ }
+ `
+ });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'main'
+ }
+ });
+ const layout1 = pipeline.getBindGroupLayout(0);
+ const layout2 = pipeline.getBindGroupLayout(0);
+ t.expect(layout1 !== layout2);
+
+ layout1.label = 'foo';
+ layout2.label = 'bar';
+
+ t.expect(layout1.label === 'foo');
+ t.expect(layout2.label === 'bar');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.js
new file mode 100644
index 0000000000..7687ba27cd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/buffer_sync_test.js
@@ -0,0 +1,942 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../../../common/util/util.js';import { GPUTest } from '../../../../gpu_test.js';import { checkElementsEqualEither } from '../../../../util/check_contents.js';
+
+
+export const kAllWriteOps = ['storage', 'b2b-copy', 't2b-copy', 'write-buffer'];
+
+export const kAllReadOps = [
+'input-vertex',
+'input-index',
+'input-indirect',
+'input-indirect-index',
+'input-indirect-dispatch',
+
+'constant-uniform',
+
+'storage-read',
+
+'b2b-copy',
+'b2t-copy'];
+
+
+
+
+
+
+
+
+
+
+
+const kOpInfo =
+
+{
+ 'write-buffer': {
+ contexts: ['queue']
+ },
+ 'b2t-copy': {
+ contexts: ['command-encoder']
+ },
+ 'b2b-copy': {
+ contexts: ['command-encoder']
+ },
+ 't2b-copy': {
+ contexts: ['command-encoder']
+ },
+ storage: {
+ contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder']
+ },
+ 'storage-read': {
+ contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder']
+ },
+ 'input-vertex': {
+ contexts: ['render-pass-encoder', 'render-bundle-encoder']
+ },
+ 'input-index': {
+ contexts: ['render-pass-encoder', 'render-bundle-encoder']
+ },
+ 'input-indirect': {
+ contexts: ['render-pass-encoder', 'render-bundle-encoder']
+ },
+ 'input-indirect-index': {
+ contexts: ['render-pass-encoder', 'render-bundle-encoder']
+ },
+ 'input-indirect-dispatch': {
+ contexts: ['compute-pass-encoder']
+ },
+ 'constant-uniform': {
+ contexts: ['render-pass-encoder', 'render-bundle-encoder']
+ }
+};
+
+export function checkOpsValidForContext(
+ops,
+context)
+{
+ const valid =
+ kOpInfo[ops[0]].contexts.includes(context[0]) && kOpInfo[ops[1]].contexts.includes(context[1]);
+ if (!valid) return false;
+
+ if (
+ context[0] === 'render-bundle-encoder' ||
+ context[0] === 'render-pass-encoder' ||
+ context[1] === 'render-bundle-encoder' ||
+ context[1] === 'render-pass-encoder')
+ {
+ // In a render pass, it is invalid to use a resource as both writable and another usage.
+ // Also, for storage+storage usage, the application is opting into racy behavior.
+ // The storage+storage case is also skipped as the results cannot be reliably tested.
+ const checkImpl = (op1, op2) => {
+ switch (op1) {
+ case 'storage':
+ switch (op2) {
+ case 'storage':
+ case 'storage-read':
+ case 'input-vertex':
+ case 'input-index':
+ case 'input-indirect':
+ case 'input-indirect-index':
+ case 'constant-uniform':
+ // Write+other, or racy.
+ return false;
+ case 'b2t-copy':
+ case 't2b-copy':
+ case 'b2b-copy':
+ case 'write-buffer':
+ // These don't occur in a render pass.
+ return true;
+ }
+ break;
+ case 'input-vertex':
+ case 'input-index':
+ case 'input-indirect':
+ case 'input-indirect-index':
+ case 'constant-uniform':
+ case 'b2t-copy':
+ case 't2b-copy':
+ case 'b2b-copy':
+ case 'write-buffer':
+ // These are not write usages, or don't occur in a render pass.
+ break;
+ }
+ return true;
+ };
+ return checkImpl(ops[0], ops[1]) && checkImpl(ops[1], ops[0]);
+ }
+ return true;
+}
+
+const kDummyVertexShader = `
+@vertex fn vert_main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.5, 0.5, 0.0, 1.0);
+}
+`;
+
+// Note: If it would be useful to have any of these helpers be separate from the fixture,
+// they can be refactored into standalone functions.
+export class BufferSyncTest extends GPUTest {
+ // Vertex and index buffers used in read render pass
+
+
+
+ // Temp buffer and texture with values for buffer/texture copy write op
+ // There can be at most 2 write op
+ tmpValueBuffers = [undefined, undefined];
+ tmpValueTextures = [undefined, undefined];
+
+ // These intermediate buffers/textures are created before any read/write op
+ // to avoid extra memory synchronization between ops introduced by await on buffer/texture creations.
+ // Create extra buffers/textures needed by write operation
+ async createIntermediateBuffersAndTexturesForWriteOp(
+ writeOp,
+ slot,
+ value)
+ {
+ switch (writeOp) {
+ case 'b2b-copy':
+ this.tmpValueBuffers[slot] = await this.createBufferWithValue(value);
+ break;
+ case 't2b-copy':
+ this.tmpValueTextures[slot] = await this.createTextureWithValue(value);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Create extra buffers/textures needed by read operation
+ async createBuffersForReadOp(readOp, srcValue, opValue) {
+ // This helps create values that will be written into dst buffer by the readop
+ switch (readOp) {
+ case 'input-index':
+ // The index buffer will be the src buffer of the read op.
+ // The src value for readOp will be 0
+ // If the index buffer value is 0, the src value is written into the dst buffer.
+ // If the index buffer value is 1, the op value is written into the dst buffer.
+ this.vertexBuffer = await this.createBufferWithValues([srcValue, opValue]);
+ break;
+ case 'input-indirect':
+ // The indirect buffer for the draw cmd will be the src buffer of the read op.
+ // If the first value in the indirect buffer is 1, then the op value in vertex buffer will be written into dst buffer.
+ // If the first value in indirect buffer is 0, then nothing will be write into dst buffer.
+ this.vertexBuffer = await this.createBufferWithValues([opValue]);
+ break;
+ case 'input-indirect-index':
+ // The indirect buffer for draw indexed cmd will be the src buffer of the read op.
+ // If the first value in the indirect buffer is 1, then the opValue in vertex buffer will be written into dst buffer.
+ // If the first value in indirect buffer is 0, then nothing will be write into dst buffer.
+ this.vertexBuffer = await this.createBufferWithValues([opValue]);
+ this.indexBuffer = await this.createBufferWithValues([0]);
+ break;
+ default:
+ break;
+ }
+
+ let srcBuffer;
+ switch (readOp) {
+ case 'input-indirect':
+ // vertexCount = {0, 1}
+ // instanceCount = 1
+ // firstVertex = 0
+ // firstInstance = 0
+ srcBuffer = await this.createBufferWithValues([srcValue, 1, 0, 0]);
+ break;
+ case 'input-indirect-index':
+ // indexCount = {0, 1}
+ // instanceCount = 1
+ // firstIndex = 0
+ // baseVertex = 0
+ // firstInstance = 0
+ srcBuffer = await this.createBufferWithValues([srcValue, 1, 0, 0, 0]);
+ break;
+ case 'input-indirect-dispatch':
+ // workgroupCountX = {0, 1}
+ // workgroupCountY = 1
+ // workgroupCountZ = 1
+ srcBuffer = await this.createBufferWithValues([srcValue, 1, 1]);
+ break;
+ default:
+ srcBuffer = await this.createBufferWithValue(srcValue);
+ break;
+ }
+
+ const dstBuffer = this.trackForCleanup(
+ this.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage:
+ GPUBufferUsage.COPY_SRC |
+ GPUBufferUsage.COPY_DST |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX |
+ GPUBufferUsage.INDIRECT |
+ GPUBufferUsage.UNIFORM
+ })
+ );
+
+ return { srcBuffer, dstBuffer };
+ }
+
+ // Create a buffer with 1 uint32 element, and initialize it to a specified value.
+ async createBufferWithValue(initValue) {
+ const buffer = this.trackForCleanup(
+ this.device.createBuffer({
+ mappedAtCreation: true,
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage:
+ GPUBufferUsage.COPY_SRC |
+ GPUBufferUsage.COPY_DST |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX |
+ GPUBufferUsage.INDIRECT |
+ GPUBufferUsage.UNIFORM
+ })
+ );
+ new Uint32Array(buffer.getMappedRange()).fill(initValue);
+ buffer.unmap();
+ await this.queue.onSubmittedWorkDone();
+ return buffer;
+ }
+
+ // Create a buffer, and initialize it to the specified values.
+ async createBufferWithValues(initValues) {
+ const buffer = this.trackForCleanup(
+ this.device.createBuffer({
+ mappedAtCreation: true,
+ size: Uint32Array.BYTES_PER_ELEMENT * initValues.length,
+ usage:
+ GPUBufferUsage.COPY_SRC |
+ GPUBufferUsage.COPY_DST |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX |
+ GPUBufferUsage.INDIRECT |
+ GPUBufferUsage.UNIFORM
+ })
+ );
+ const bufferView = new Uint32Array(buffer.getMappedRange());
+ bufferView.set(initValues);
+ buffer.unmap();
+ await this.queue.onSubmittedWorkDone();
+ return buffer;
+ }
+
+ // Create a 1x1 texture, and initialize it to a specified value for all elements.
+ async createTextureWithValue(initValue) {
+ // This is not hot in profiles; optimize if this gets used more heavily.
+ const data = new Uint32Array(1).fill(initValue);
+ const texture = this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'r32uint',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ })
+ );
+ this.device.queue.writeTexture(
+ { texture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ data,
+ { offset: 0, bytesPerRow: 256, rowsPerImage: 1 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ await this.queue.onSubmittedWorkDone();
+ return texture;
+ }
+
+ createBindGroup(
+ pipeline,
+ buffer)
+ {
+ return this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer } }]
+ });
+ }
+
+ // Create a compute pipeline and write given data into storage buffer.
+ createStorageWriteComputePipeline(value) {
+ const wgslCompute = `
+ struct Data {
+ a : u32
+ };
+
+ @group(0) @binding(0) var<storage, read_write> data : Data;
+ @compute @workgroup_size(1) fn main() {
+ data.a = ${value}u;
+ }
+ `;
+
+ return this.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: this.device.createShaderModule({
+ code: wgslCompute
+ }),
+ entryPoint: 'main'
+ }
+ });
+ }
+
+ createTrivialRenderPipeline(wgslShaders) {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.vertex
+ }),
+ entryPoint: 'vert_main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.fragment
+ }),
+ entryPoint: 'frag_main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+ }
+
+ // Create a render pipeline and write given data into storage buffer at fragment stage.
+ createStorageWriteRenderPipeline(value) {
+ const wgslShaders = {
+ vertex: kDummyVertexShader,
+ fragment: `
+ struct Data {
+ a : u32
+ };
+
+ @group(0) @binding(0) var<storage, read_write> data : Data;
+ @fragment fn frag_main() -> @location(0) vec4<f32> {
+ data.a = ${value}u;
+ return vec4<f32>(); // result does't matter
+ }
+ `
+ };
+
+ return this.createTrivialRenderPipeline(wgslShaders);
+ }
+
+ beginSimpleRenderPass(encoder) {
+ const view = this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ ).createView();
+ return encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view,
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+
+ // Write buffer via draw call in render pass. Use bundle if needed.
+ encodeWriteAsStorageBufferInRenderPass(
+ renderer,
+ buffer,
+ value)
+ {
+ const pipeline = this.createStorageWriteRenderPipeline(value);
+ const bindGroup = this.createBindGroup(pipeline, buffer);
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.draw(1, 1, 0, 0);
+ }
+
+ // Write buffer via dispatch call in compute pass.
+ encodeWriteAsStorageBufferInComputePass(
+ pass,
+ buffer,
+ value)
+ {
+ const pipeline = this.createStorageWriteComputePipeline(value);
+ const bindGroup = this.createBindGroup(pipeline, buffer);
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ }
+
+ // Write buffer via BufferToBuffer copy.
+ encodeWriteByB2BCopy(encoder, buffer, slot) {
+ const tmpBuffer = this.tmpValueBuffers[slot];
+ assert(tmpBuffer !== undefined);
+ // The write operation via b2b copy is just encoded into command encoder, it doesn't write immediately.
+ encoder.copyBufferToBuffer(tmpBuffer, 0, buffer, 0, Uint32Array.BYTES_PER_ELEMENT);
+ }
+
+ // Write buffer via TextureToBuffer copy.
+ encodeWriteByT2BCopy(encoder, buffer, slot) {
+ const tmpTexture = this.tmpValueTextures[slot];
+ assert(tmpTexture !== undefined);
+ // The write operation via t2b copy is just encoded into command encoder, it doesn't write immediately.
+ encoder.copyTextureToBuffer(
+ { texture: tmpTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ }
+
+ // Write buffer via writeBuffer API on queue
+ writeByWriteBuffer(buffer, value) {
+ // This is not hot in profiles; optimize if this gets used more heavily.
+ const data = new Uint32Array(1).fill(value);
+ this.device.queue.writeBuffer(buffer, 0, data);
+ }
+
+ // Issue write operation via render pass, compute pass, copy, etc.
+ encodeWriteOp(
+ helper,
+ operation,
+ context,
+ buffer,
+ writeOpSlot,
+ value)
+ {
+ helper.ensureContext(context);
+
+ switch (operation) {
+ case 'write-buffer':
+ this.writeByWriteBuffer(buffer, value);
+ break;
+ case 'storage':
+ switch (context) {
+ case 'render-pass-encoder':
+ assert(helper.renderPassEncoder !== undefined);
+ this.encodeWriteAsStorageBufferInRenderPass(helper.renderPassEncoder, buffer, value);
+ break;
+ case 'render-bundle-encoder':
+ assert(helper.renderBundleEncoder !== undefined);
+ this.encodeWriteAsStorageBufferInRenderPass(helper.renderBundleEncoder, buffer, value);
+ break;
+ case 'compute-pass-encoder':
+ assert(helper.computePassEncoder !== undefined);
+ this.encodeWriteAsStorageBufferInComputePass(helper.computePassEncoder, buffer, value);
+ break;
+ default:
+ unreachable();
+ }
+ break;
+ case 'b2b-copy':
+ assert(helper.commandEncoder !== undefined);
+ this.encodeWriteByB2BCopy(helper.commandEncoder, buffer, writeOpSlot);
+ break;
+ case 't2b-copy':
+ assert(helper.commandEncoder !== undefined);
+ this.encodeWriteByT2BCopy(helper.commandEncoder, buffer, writeOpSlot);
+ break;
+ default:
+ unreachable();
+ }
+ }
+
+ // Create a compute pipeline: read from src buffer and write it into the storage buffer.
+ createStorageReadComputePipeline() {
+ const wgslCompute = `
+ struct Data {
+ a : u32
+ };
+
+ @group(0) @binding(0) var<storage, read> srcData : Data;
+ @group(0) @binding(1) var<storage, read_write> dstData : Data;
+
+ @compute @workgroup_size(1) fn main() {
+ dstData.a = srcData.a;
+ }
+ `;
+
+ return this.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: this.device.createShaderModule({
+ code: wgslCompute
+ }),
+ entryPoint: 'main'
+ }
+ });
+ }
+
+ createBindGroupSrcDstBuffer(
+ pipeline,
+ srcBuffer,
+ dstBuffer)
+ {
+ return this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: srcBuffer } },
+ { binding: 1, resource: { buffer: dstBuffer } }]
+
+ });
+ }
+
+ // Create a render pipeline: read from vertex/index buffer and write it into the storage dst buffer at fragment stage.
+ createVertexReadRenderPipeline() {
+ const wgslShaders = {
+ vertex: `
+ struct VertexOutput {
+ @builtin(position) position : vec4<f32>,
+ @location(0) @interpolate(flat) data : u32,
+ };
+
+ @vertex fn vert_main(@location(0) input: u32) -> VertexOutput {
+ var output : VertexOutput;
+ output.position = vec4<f32>(0.5, 0.5, 0.0, 1.0);
+ output.data = input;
+ return output;
+ }
+ `,
+ fragment: `
+ struct Data {
+ a : u32
+ };
+
+ @group(0) @binding(0) var<storage, read_write> data : Data;
+
+ @fragment fn frag_main(@location(0) @interpolate(flat) input : u32) -> @location(0) vec4<f32> {
+ data.a = input;
+ return vec4<f32>(); // result does't matter
+ }
+ `
+ };
+
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.vertex
+ }),
+ entryPoint: 'vert_main',
+ buffers: [
+ {
+ arrayStride: Uint32Array.BYTES_PER_ELEMENT,
+ attributes: [
+ {
+ shaderLocation: 0,
+ offset: 0,
+ format: 'uint32'
+ }]
+
+ }]
+
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.fragment
+ }),
+ entryPoint: 'frag_main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+ }
+
+ // Create a render pipeline: read from uniform buffer and write it into the storage dst buffer at fragment stage.
+ createUniformReadRenderPipeline() {
+ const wgslShaders = {
+ vertex: kDummyVertexShader,
+ fragment: `
+ struct Data {
+ a : u32
+ };
+
+ @group(0) @binding(0) var<uniform> constant: Data;
+ @group(0) @binding(1) var<storage, read_write> data : Data;
+
+ @fragment fn frag_main() -> @location(0) vec4<f32> {
+ data.a = constant.a;
+ return vec4<f32>(); // result does't matter
+ }
+ `
+ };
+
+ return this.createTrivialRenderPipeline(wgslShaders);
+ }
+
+ // Create a render pipeline: read from storage src buffer and write it into the storage dst buffer at fragment stage.
+ createStorageReadRenderPipeline() {
+ const wgslShaders = {
+ vertex: kDummyVertexShader,
+ fragment: `
+ struct Data {
+ a : u32
+ };
+
+ @group(0) @binding(0) var<storage, read> srcData : Data;
+ @group(0) @binding(1) var<storage, read_write> dstData : Data;
+
+ @fragment fn frag_main() -> @location(0) vec4<f32> {
+ dstData.a = srcData.a;
+ return vec4<f32>(); // result does't matter
+ }
+ `
+ };
+
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.vertex
+ }),
+ entryPoint: 'vert_main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: wgslShaders.fragment
+ }),
+ entryPoint: 'frag_main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+ }
+
+ // Write buffer via dispatch call in compute pass.
+ encodeReadAsStorageBufferInComputePass(
+ pass,
+ srcBuffer,
+ dstBuffer)
+ {
+ const pipeline = this.createStorageReadComputePipeline();
+ const bindGroup = this.createBindGroupSrcDstBuffer(pipeline, srcBuffer, dstBuffer);
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ }
+
+ // Write buffer via dispatchWorkgroupsIndirect call in compute pass.
+ encodeReadAsIndirectBufferInComputePass(
+ pass,
+ srcBuffer,
+ dstBuffer,
+ value)
+ {
+ const pipeline = this.createStorageWriteComputePipeline(value);
+ const bindGroup = this.createBindGroup(pipeline, dstBuffer);
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroupsIndirect(srcBuffer, 0);
+ }
+
+ // Read as vertex input and write buffer via draw call in render pass. Use bundle if needed.
+ encodeReadAsVertexBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer)
+ {
+ const pipeline = this.createVertexReadRenderPipeline();
+ const bindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: dstBuffer } }]
+ });
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.setVertexBuffer(0, srcBuffer);
+ renderer.draw(1);
+ }
+
+ // Read as index input and write buffer via draw call in render pass. Use bundle if needed.
+ encodeReadAsIndexBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer,
+ vertexBuffer)
+ {
+ const pipeline = this.createVertexReadRenderPipeline();
+ const bindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: dstBuffer } }]
+ });
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.setVertexBuffer(0, vertexBuffer);
+ renderer.setIndexBuffer(srcBuffer, 'uint32');
+ renderer.drawIndexed(1);
+ }
+
+ // Read as indirect input and write buffer via draw call in render pass. Use bundle if needed.
+ encodeReadAsIndirectBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer,
+ vertexBuffer)
+ {
+ const pipeline = this.createVertexReadRenderPipeline();
+ const bindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: dstBuffer } }]
+ });
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.setVertexBuffer(0, vertexBuffer);
+ renderer.drawIndirect(srcBuffer, 0);
+ }
+
+ // Read as indexed indirect input and write buffer via draw call in render pass. Use bundle if needed.
+ encodeReadAsIndexedIndirectBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer,
+ vertexBuffer,
+ indexBuffer)
+ {
+ const pipeline = this.createVertexReadRenderPipeline();
+ const bindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: dstBuffer } }]
+ });
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.setVertexBuffer(0, vertexBuffer);
+ renderer.setIndexBuffer(indexBuffer, 'uint32');
+ renderer.drawIndexedIndirect(srcBuffer, 0);
+ }
+
+ // Read as uniform buffer and write buffer via draw call in render pass. Use bundle if needed.
+ encodeReadAsUniformBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer)
+ {
+ const pipeline = this.createUniformReadRenderPipeline();
+ const bindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: srcBuffer } },
+ { binding: 1, resource: { buffer: dstBuffer } }]
+
+ });
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.draw(1);
+ }
+
+ // Read as storage buffer and write buffer via draw call in render pass. Use bundle if needed.
+ encodeReadAsStorageBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer)
+ {
+ const pipeline = this.createStorageReadRenderPipeline();
+ const bindGroup = this.createBindGroupSrcDstBuffer(pipeline, srcBuffer, dstBuffer);
+
+ renderer.setBindGroup(0, bindGroup);
+ renderer.setPipeline(pipeline);
+ renderer.draw(1, 1, 0, 0);
+ }
+
+ // Read and write via BufferToBuffer copy.
+ encodeReadByB2BCopy(encoder, srcBuffer, dstBuffer) {
+ // The b2b copy is just encoded into command encoder, it doesn't write immediately.
+ encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, Uint32Array.BYTES_PER_ELEMENT);
+ }
+
+ // Read and Write texture via BufferToTexture copy.
+ encodeReadByB2TCopy(encoder, srcBuffer, dstBuffer) {
+ const tmpTexture = this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'r32uint',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ })
+ );
+
+ // The b2t copy is just encoded into command encoder, it doesn't write immediately.
+ encoder.copyBufferToTexture(
+ { buffer: srcBuffer, bytesPerRow: 256 },
+ { texture: tmpTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ // The t2b copy is just encoded into command encoder, it doesn't write immediately.
+ encoder.copyTextureToBuffer(
+ { texture: tmpTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dstBuffer, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ }
+
+ encodeReadOp(
+ helper,
+ operation,
+ context,
+ srcBuffer,
+ dstBuffer)
+ {
+ helper.ensureContext(context);
+
+ const renderer =
+ context === 'render-bundle-encoder' ? helper.renderBundleEncoder : helper.renderPassEncoder;
+ const computePass = context === 'compute-pass-encoder' ? helper.computePassEncoder : undefined;
+
+ switch (operation) {
+ case 'input-vertex':
+ // The srcBuffer is used as vertexBuffer.
+ // draw writes the same value in srcBuffer[0] to dstBuffer[0].
+ assert(renderer !== undefined);
+ this.encodeReadAsVertexBufferInRenderPass(renderer, srcBuffer, dstBuffer);
+ break;
+ case 'input-index':
+ // The srcBuffer is used as indexBuffer.
+ // With this vertexBuffer, drawIndexed writes the same value in srcBuffer[0] to dstBuffer[0].
+ assert(renderer !== undefined);
+ assert(this.vertexBuffer !== undefined);
+ this.encodeReadAsIndexBufferInRenderPass(renderer, srcBuffer, dstBuffer, this.vertexBuffer);
+ break;
+ case 'input-indirect':
+ // The srcBuffer is used as indirectBuffer for drawIndirect.
+ // srcBuffer[0] = 0 or 1 (vertexCount), which will decide the value written into dstBuffer to be either 0 or 1.
+ assert(renderer !== undefined);
+ assert(this.vertexBuffer !== undefined);
+ this.encodeReadAsIndirectBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer,
+ this.vertexBuffer
+ );
+ break;
+ case 'input-indirect-index':
+ // The srcBuffer is used as indirectBuffer for drawIndexedIndirect.
+ // srcBuffer[0] = 0 or 1 (indexCount), which will decide the value written into dstBuffer to be either 0 or 1.
+ assert(renderer !== undefined);
+ assert(this.vertexBuffer !== undefined);
+ assert(this.indexBuffer !== undefined);
+ this.encodeReadAsIndexedIndirectBufferInRenderPass(
+ renderer,
+ srcBuffer,
+ dstBuffer,
+ this.vertexBuffer,
+ this.indexBuffer
+ );
+ break;
+ case 'input-indirect-dispatch':
+ // The srcBuffer is used as indirectBuffer for dispatch.
+ // srcBuffer[0] = 0 or 1 (workgroupCountX), which will decide the value written into dstBuffer to be either 0 or 1.
+ assert(computePass !== undefined);
+ this.encodeReadAsIndirectBufferInComputePass(computePass, srcBuffer, dstBuffer, 1);
+ break;
+ case 'constant-uniform':
+ // The srcBuffer is used as uniform buffer.
+ assert(renderer !== undefined);
+ this.encodeReadAsUniformBufferInRenderPass(renderer, srcBuffer, dstBuffer);
+ break;
+ case 'storage-read':
+ switch (context) {
+ case 'render-pass-encoder':
+ case 'render-bundle-encoder':
+ assert(renderer !== undefined);
+ this.encodeReadAsStorageBufferInRenderPass(renderer, srcBuffer, dstBuffer);
+ break;
+ case 'compute-pass-encoder':
+ assert(computePass !== undefined);
+ this.encodeReadAsStorageBufferInComputePass(computePass, srcBuffer, dstBuffer);
+ break;
+ default:
+ unreachable();
+ }
+ break;
+ case 'b2b-copy':
+ assert(helper.commandEncoder !== undefined);
+ this.encodeReadByB2BCopy(helper.commandEncoder, srcBuffer, dstBuffer);
+ break;
+ case 'b2t-copy':
+ assert(helper.commandEncoder !== undefined);
+ this.encodeReadByB2TCopy(helper.commandEncoder, srcBuffer, dstBuffer);
+ break;
+ default:
+ unreachable();
+ }
+ }
+
+ verifyData(buffer, expectedValue) {
+ // This is not hot in profiles; optimize if this gets used more heavily.
+ const bufferData = new Uint32Array(1);
+ bufferData[0] = expectedValue;
+ this.expectGPUBufferValuesEqual(buffer, bufferData);
+ }
+
+ verifyDataTwoValidValues(buffer, expectedValue1, expectedValue2) {
+ // This is not hot in profiles; optimize if this gets used more heavily.
+ const bufferData1 = new Uint32Array(1);
+ bufferData1[0] = expectedValue1;
+ const bufferData2 = new Uint32Array(1);
+ bufferData2[0] = expectedValue2;
+ this.expectGPUBufferValuesPassCheck(
+ buffer,
+ (a) => checkElementsEqualEither(a, [bufferData1, bufferData2]),
+ { type: Uint32Array, typedLength: 1 }
+ );
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/multiple_buffers.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/multiple_buffers.spec.js
new file mode 100644
index 0000000000..01be4c06c7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/multiple_buffers.spec.js
@@ -0,0 +1,354 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Memory Synchronization Tests for multiple buffers: read before write, read after write, and write after write.
+
+- Create multiple src buffers and initialize it to 0, wait on the fence to ensure the data is initialized.
+Write Op: write a value (say 1) into the src buffer via render pass, compute pass, copy, write buffer, etc.
+Read Op: read the value from the src buffer and write it to dst buffer via render pass (vertex, index, indirect input, uniform, storage), compute pass, copy etc.
+Wait on another fence, then call expectContents to verify the dst buffer value.
+ - x= write op: {storage buffer in {compute, render, render-via-bundle}, t2b copy dst, b2b copy dst, writeBuffer}
+ - x= read op: {index buffer, vertex buffer, indirect buffer (draw, draw indexed, dispatch), uniform buffer, {readonly, readwrite} storage buffer in {compute, render, render-via-bundle}, b2b copy src, b2t copy src}
+ - x= read-write sequence: {read then write, write then read, write then write}
+ - x= op context: {queue, command-encoder, compute-pass-encoder, render-pass-encoder, render-bundle-encoder}, x= op boundary: {queue-op, command-buffer, pass, execute-bundles, render-bundle}
+ - Not every context/boundary combinations are valid. We have the checkOpsValidForContext func to do the filtering.
+ - If two writes are in the same passes, render result has loose guarantees.
+
+TODO: Tests with more than one buffer to try to stress implementations a little bit more.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import {
+ kOperationBoundaries,
+ kBoundaryInfo,
+ OperationContextHelper } from
+'../operation_context_helper.js';
+
+import {
+ kAllReadOps,
+ kAllWriteOps,
+ BufferSyncTest,
+ checkOpsValidForContext } from
+'./buffer_sync_test.js';
+
+// The src value is what stores in the src buffer before any operation.
+const kSrcValue = 0;
+// The op value is what the read/write operation write into the target buffer.
+const kOpValue = 1;
+
+export const g = makeTestGroup(BufferSyncTest);
+
+g.test('rw').
+desc(
+ `
+ Perform a 'read' operations on multiple buffers, followed by a 'write' operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The read should not see the contents written by the subsequent write.
+ `
+).
+params((u) =>
+u //
+.combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const readOp of kAllReadOps) {
+ for (const writeOp of kAllWriteOps) {
+ if (checkOpsValidForContext([readOp, writeOp], _context)) {
+ yield {
+ readOp,
+ readContext: _context[0],
+ writeOp,
+ writeContext: _context[1]
+ };
+ }
+ }
+ }
+})
+).
+fn(async (t) => {
+ const { readContext, readOp, writeContext, writeOp, boundary } = t.params;
+ const helper = new OperationContextHelper(t);
+
+ const srcBuffers = [];
+ const dstBuffers = [];
+
+ const kBufferCount = 4;
+ for (let i = 0; i < kBufferCount; i++) {
+ const { srcBuffer, dstBuffer } = await t.createBuffersForReadOp(readOp, kSrcValue, kOpValue);
+ srcBuffers.push(srcBuffer);
+ dstBuffers.push(dstBuffer);
+ }
+
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOp, 0, kOpValue);
+
+ // The read op will read from src buffers and write to dst buffers based on what it reads.
+ // A boundary will separate multiple read and write operations. The write op will write the
+ // given op value into each src buffer as well. The write op happens after read op. So we are
+ // expecting each src value to be in the mapped dst buffer.
+ for (let i = 0; i < kBufferCount; i++) {
+ t.encodeReadOp(helper, readOp, readContext, srcBuffers[i], dstBuffers[i]);
+ }
+
+ helper.ensureBoundary(boundary);
+
+ for (let i = 0; i < kBufferCount; i++) {
+ t.encodeWriteOp(helper, writeOp, writeContext, srcBuffers[i], 0, kOpValue);
+ }
+
+ helper.ensureSubmit();
+
+ for (let i = 0; i < kBufferCount; i++) {
+ // Only verify the value of the first element of each dstBuffer.
+ t.verifyData(dstBuffers[i], kSrcValue);
+ }
+});
+
+g.test('wr').
+desc(
+ `
+ Perform a 'write' operation on on multiple buffers, followed by a 'read' operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The read should see exactly the contents written by the previous write.`
+).
+params((u) =>
+u //
+.combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const readOp of kAllReadOps) {
+ for (const writeOp of kAllWriteOps) {
+ if (checkOpsValidForContext([readOp, writeOp], _context)) {
+ yield {
+ readOp,
+ readContext: _context[0],
+ writeOp,
+ writeContext: _context[1]
+ };
+ }
+ }
+ }
+})
+).
+fn(async (t) => {
+ const { readContext, readOp, writeContext, writeOp, boundary } = t.params;
+ const helper = new OperationContextHelper(t);
+
+ const srcBuffers = [];
+ const dstBuffers = [];
+
+ const kBufferCount = 4;
+
+ for (let i = 0; i < kBufferCount; i++) {
+ const { srcBuffer, dstBuffer } = await t.createBuffersForReadOp(readOp, kSrcValue, kOpValue);
+
+ srcBuffers.push(srcBuffer);
+ dstBuffers.push(dstBuffer);
+ }
+
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOp, 0, kOpValue);
+
+ // The write op will write the given op value into src buffers.
+ // The read op will read from src buffers and write to dst buffers based on what it reads.
+ // The write op happens before read op. So we are expecting the op value to be in the dst
+ // buffers.
+ for (let i = 0; i < kBufferCount; i++) {
+ t.encodeWriteOp(helper, writeOp, writeContext, srcBuffers[i], 0, kOpValue);
+ }
+
+ helper.ensureBoundary(boundary);
+
+ for (let i = 0; i < kBufferCount; i++) {
+ t.encodeReadOp(helper, readOp, readContext, srcBuffers[i], dstBuffers[i]);
+ }
+
+ helper.ensureSubmit();
+
+ for (let i = 0; i < kBufferCount; i++) {
+ // Only verify the value of the first element of the dstBuffer
+ t.verifyData(dstBuffers[i], kOpValue);
+ }
+});
+
+g.test('ww').
+desc(
+ `
+ Perform a 'first' write operation on multiple buffers, followed by a 'second' write operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The second write should overwrite the contents of the first.`
+).
+params((u) =>
+u //
+.combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const firstWriteOp of kAllWriteOps) {
+ for (const secondWriteOp of kAllWriteOps) {
+ if (checkOpsValidForContext([firstWriteOp, secondWriteOp], _context)) {
+ yield {
+ writeOps: [firstWriteOp, secondWriteOp],
+ contexts: _context
+ };
+ }
+ }
+ }
+})
+).
+fn(async (t) => {
+ const { writeOps, contexts, boundary } = t.params;
+ const helper = new OperationContextHelper(t);
+
+ const buffers = [];
+
+ const kBufferCount = 4;
+
+ for (let i = 0; i < kBufferCount; i++) {
+ const buffer = await t.createBufferWithValue(0);
+
+ buffers.push(buffer);
+ }
+
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOps[0], 0, 1);
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOps[1], 1, 2);
+
+ for (let i = 0; i < kBufferCount; i++) {
+ t.encodeWriteOp(helper, writeOps[0], contexts[0], buffers[i], 0, 1);
+ }
+
+ helper.ensureBoundary(boundary);
+
+ for (let i = 0; i < kBufferCount; i++) {
+ t.encodeWriteOp(helper, writeOps[1], contexts[1], buffers[i], 1, 2);
+ }
+
+ helper.ensureSubmit();
+
+ for (let i = 0; i < kBufferCount; i++) {
+ t.verifyData(buffers[i], 2);
+ }
+});
+
+g.test('multiple_pairs_of_draws_in_one_render_pass').
+desc(
+ `
+ Test write-after-write operations on multiple buffers via the one render pass. The first write
+ will write the buffer index * 2 + 1 into all storage buffers. The second write will write the
+ buffer index * 2 + 2 into the all buffers in the same pass. Expected data in all buffers is either
+ buffer index * 2 + 1 or buffer index * 2 + 2. It may use bundle in each draw.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('firstDrawUseBundle', [false, true]).
+combine('secondDrawUseBundle', [false, true])
+).
+fn(async (t) => {
+ const { firstDrawUseBundle, secondDrawUseBundle } = t.params;
+
+ const encoder = t.device.createCommandEncoder();
+ const passEncoder = t.beginSimpleRenderPass(encoder);
+
+ const kBufferCount = 4;
+ const buffers = [];
+ for (let b = 0; b < kBufferCount; ++b) {
+ const buffer = await t.createBufferWithValue(0);
+ buffers.push(buffer);
+
+ const useBundle = [firstDrawUseBundle, secondDrawUseBundle];
+ for (let i = 0; i < 2; ++i) {
+ const renderEncoder = useBundle[i] ?
+ t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ }) :
+ passEncoder;
+ const pipeline = t.createStorageWriteRenderPipeline(2 * b + i + 1);
+ const bindGroup = t.createBindGroup(pipeline, buffer);
+ renderEncoder.setPipeline(pipeline);
+ renderEncoder.setBindGroup(0, bindGroup);
+ renderEncoder.draw(1, 1, 0, 0);
+ if (useBundle[i])
+ passEncoder.executeBundles([renderEncoder.finish()]);
+ }
+ }
+
+ passEncoder.end();
+ t.device.queue.submit([encoder.finish()]);
+ for (let b = 0; b < kBufferCount; ++b) {
+ t.verifyDataTwoValidValues(buffers[b], 2 * b + 1, 2 * b + 2);
+ }
+});
+
+g.test('multiple_pairs_of_draws_in_one_render_bundle').
+desc(
+ `
+ Test write-after-write operations on multiple buffers via the one render bundle. The first write
+ will write the buffer index * 2 + 1 into all storage buffers. The second write will write the
+ buffer index * 2 + 2 into the all buffers in the same pass. Expected data in all buffers is either
+ buffer index * 2 + 1 or buffer index * 2 + 2.
+ `
+).
+fn(async (t) => {
+ const encoder = t.device.createCommandEncoder();
+ const passEncoder = t.beginSimpleRenderPass(encoder);
+ const renderEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ });
+
+ const kBufferCount = 4;
+ const buffers = [];
+ for (let b = 0; b < kBufferCount; ++b) {
+ const buffer = await t.createBufferWithValue(0);
+ buffers.push(buffer);
+
+ for (let i = 0; i < 2; ++i) {
+ const pipeline = t.createStorageWriteRenderPipeline(2 * b + i + 1);
+ const bindGroup = t.createBindGroup(pipeline, buffer);
+ renderEncoder.setPipeline(pipeline);
+ renderEncoder.setBindGroup(0, bindGroup);
+ renderEncoder.draw(1, 1, 0, 0);
+ }
+ }
+
+ passEncoder.executeBundles([renderEncoder.finish()]);
+ passEncoder.end();
+ t.device.queue.submit([encoder.finish()]);
+ for (let b = 0; b < kBufferCount; ++b) {
+ t.verifyDataTwoValidValues(buffers[b], 2 * b + 1, 2 * b + 2);
+ }
+});
+
+g.test('multiple_pairs_of_dispatches_in_one_compute_pass').
+desc(
+ `
+ Test write-after-write operations on multiple buffers via the one compute pass. The first write
+ will write the buffer index * 2 + 1 into all storage buffers. The second write will write the
+ buffer index * 2 + 2 into the all buffers in the same pass. Expected data in all buffers is the
+ buffer index * 2 + 2.
+ `
+).
+fn(async (t) => {
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+
+ const kBufferCount = 4;
+ const buffers = [];
+ for (let b = 0; b < kBufferCount; ++b) {
+ const buffer = await t.createBufferWithValue(0);
+ buffers.push(buffer);
+
+ for (let i = 0; i < 2; ++i) {
+ const pipeline = t.createStorageWriteComputePipeline(2 * b + i + 1);
+ const bindGroup = t.createBindGroup(pipeline, buffer);
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ }
+ }
+
+ pass.end();
+
+ t.device.queue.submit([encoder.finish()]);
+ for (let b = 0; b < kBufferCount; ++b) {
+ t.verifyData(buffers[b], 2 * b + 2);
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/single_buffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/single_buffer.spec.js
new file mode 100644
index 0000000000..8a6bb018e8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/buffer/single_buffer.spec.js
@@ -0,0 +1,257 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Memory Synchronization Tests for Buffer: read before write, read after write, and write after write.
+
+- Create a src buffer and initialize it to 0, wait on the fence to ensure the data is initialized.
+Write Op: write a value (say 1) into the src buffer via render pass, compute pass, copy, write buffer, etc.
+Read Op: read the value from the src buffer and write it to dst buffer via render pass (vertex, index, indirect input, uniform, storage), compute pass, copy etc.
+Wait on another fence, then call expectContents to verify the dst buffer value.
+ - x= write op: {storage buffer in {compute, render, render-via-bundle}, t2b copy dst, b2b copy dst, writeBuffer}
+ - x= read op: {index buffer, vertex buffer, indirect buffer (draw, draw indexed, dispatch), uniform buffer, {readonly, readwrite} storage buffer in {compute, render, render-via-bundle}, b2b copy src, b2t copy src}
+ - x= read-write sequence: {read then write, write then read, write then write}
+ - x= op context: {queue, command-encoder, compute-pass-encoder, render-pass-encoder, render-bundle-encoder}, x= op boundary: {queue-op, command-buffer, pass, execute-bundles, render-bundle}
+ - Not every context/boundary combinations are valid. We have the checkOpsValidForContext func to do the filtering.
+ - If two writes are in the same passes, render result has loose guarantees.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import {
+ kOperationBoundaries,
+ kBoundaryInfo,
+ OperationContextHelper } from
+'../operation_context_helper.js';
+
+import {
+ kAllReadOps,
+ kAllWriteOps,
+ BufferSyncTest,
+ checkOpsValidForContext } from
+'./buffer_sync_test.js';
+
+// The src value is what stores in the src buffer before any operation.
+const kSrcValue = 0;
+// The op value is what the read/write operation write into the target buffer.
+const kOpValue = 1;
+
+export const g = makeTestGroup(BufferSyncTest);
+
+g.test('rw').
+desc(
+ `
+ Perform a 'read' operations on a buffer, followed by a 'write' operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The read should not see the contents written by the subsequent write.`
+).
+params((u) =>
+u //
+.combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const readOp of kAllReadOps) {
+ for (const writeOp of kAllWriteOps) {
+ if (checkOpsValidForContext([readOp, writeOp], _context)) {
+ yield {
+ readOp,
+ readContext: _context[0],
+ writeOp,
+ writeContext: _context[1]
+ };
+ }
+ }
+ }
+})
+).
+fn(async (t) => {
+ const { readContext, readOp, writeContext, writeOp, boundary } = t.params;
+ const helper = new OperationContextHelper(t);
+
+ const { srcBuffer, dstBuffer } = await t.createBuffersForReadOp(readOp, kSrcValue, kOpValue);
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOp, 0, kOpValue);
+
+ // The read op will read from src buffer and write to dst buffer based on what it reads.
+ // The write op will write the given op value into src buffer as well.
+ // The write op happens after read op. So we are expecting the src value to be in the dst buffer.
+ t.encodeReadOp(helper, readOp, readContext, srcBuffer, dstBuffer);
+ helper.ensureBoundary(boundary);
+ t.encodeWriteOp(helper, writeOp, writeContext, srcBuffer, 0, kOpValue);
+ helper.ensureSubmit();
+ // Only verify the value of the first element of the dstBuffer
+ t.verifyData(dstBuffer, kSrcValue);
+});
+
+g.test('wr').
+desc(
+ `
+ Perform a 'write' operation on a buffer, followed by a 'read' operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The read should see exactly the contents written by the previous write.`
+).
+params((u) =>
+u //
+.combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const readOp of kAllReadOps) {
+ for (const writeOp of kAllWriteOps) {
+ if (checkOpsValidForContext([readOp, writeOp], _context)) {
+ yield {
+ readOp,
+ readContext: _context[0],
+ writeOp,
+ writeContext: _context[1]
+ };
+ }
+ }
+ }
+})
+).
+fn(async (t) => {
+ const { readContext, readOp, writeContext, writeOp, boundary } = t.params;
+ const helper = new OperationContextHelper(t);
+
+ const { srcBuffer, dstBuffer } = await t.createBuffersForReadOp(readOp, kSrcValue, kOpValue);
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOp, 0, kOpValue);
+
+ // The write op will write the given op value into src buffer.
+ // The read op will read from src buffer and write to dst buffer based on what it reads.
+ // The write op happens before read op. So we are expecting the op value to be in the dst buffer.
+ t.encodeWriteOp(helper, writeOp, writeContext, srcBuffer, 0, kOpValue);
+ helper.ensureBoundary(boundary);
+ t.encodeReadOp(helper, readOp, readContext, srcBuffer, dstBuffer);
+ helper.ensureSubmit();
+ // Only verify the value of the first element of the dstBuffer
+ t.verifyData(dstBuffer, kOpValue);
+});
+
+g.test('ww').
+desc(
+ `
+ Perform a 'first' write operation on a buffer, followed by a 'second' write operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The second write should overwrite the contents of the first.`
+).
+params((u) =>
+u //
+.combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const firstWriteOp of kAllWriteOps) {
+ for (const secondWriteOp of kAllWriteOps) {
+ if (checkOpsValidForContext([firstWriteOp, secondWriteOp], _context)) {
+ yield {
+ writeOps: [firstWriteOp, secondWriteOp],
+ contexts: _context
+ };
+ }
+ }
+ }
+})
+).
+fn(async (t) => {
+ const { writeOps, contexts, boundary } = t.params;
+ const helper = new OperationContextHelper(t);
+
+ const buffer = await t.createBufferWithValue(0);
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOps[0], 0, 1);
+ await t.createIntermediateBuffersAndTexturesForWriteOp(writeOps[1], 1, 2);
+
+ t.encodeWriteOp(helper, writeOps[0], contexts[0], buffer, 0, 1);
+ helper.ensureBoundary(boundary);
+ t.encodeWriteOp(helper, writeOps[1], contexts[1], buffer, 1, 2);
+ helper.ensureSubmit();
+ t.verifyData(buffer, 2);
+});
+
+// Cases with loose render result guarantees.
+
+g.test('two_draws_in_the_same_render_pass').
+desc(
+ `Test write-after-write operations in the same render pass. The first write will write 1 into
+ a storage buffer. The second write will write 2 into the same buffer in the same pass. Expected
+ data in buffer is either 1 or 2. It may use bundle in each draw.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('firstDrawUseBundle', [false, true]).
+combine('secondDrawUseBundle', [false, true])
+).
+fn(async (t) => {
+ const { firstDrawUseBundle, secondDrawUseBundle } = t.params;
+ const buffer = await t.createBufferWithValue(0);
+ const encoder = t.device.createCommandEncoder();
+ const passEncoder = t.beginSimpleRenderPass(encoder);
+
+ const useBundle = [firstDrawUseBundle, secondDrawUseBundle];
+ for (let i = 0; i < 2; ++i) {
+ const renderEncoder = useBundle[i] ?
+ t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ }) :
+ passEncoder;
+ const pipeline = t.createStorageWriteRenderPipeline(i + 1);
+ const bindGroup = t.createBindGroup(pipeline, buffer);
+ renderEncoder.setPipeline(pipeline);
+ renderEncoder.setBindGroup(0, bindGroup);
+ renderEncoder.draw(1, 1, 0, 0);
+ if (useBundle[i])
+ passEncoder.executeBundles([renderEncoder.finish()]);
+ }
+
+ passEncoder.end();
+ t.device.queue.submit([encoder.finish()]);
+ t.verifyDataTwoValidValues(buffer, 1, 2);
+});
+
+g.test('two_draws_in_the_same_render_bundle').
+desc(
+ `Test write-after-write operations in the same render bundle. The first write will write 1 into
+ a storage buffer. The second write will write 2 into the same buffer in the same pass. Expected
+ data in buffer is either 1 or 2.`
+).
+fn(async (t) => {
+ const buffer = await t.createBufferWithValue(0);
+ const encoder = t.device.createCommandEncoder();
+ const passEncoder = t.beginSimpleRenderPass(encoder);
+ const renderEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ });
+
+ for (let i = 0; i < 2; ++i) {
+ const pipeline = t.createStorageWriteRenderPipeline(i + 1);
+ const bindGroup = t.createBindGroup(pipeline, buffer);
+ renderEncoder.setPipeline(pipeline);
+ renderEncoder.setBindGroup(0, bindGroup);
+ renderEncoder.draw(1, 1, 0, 0);
+ }
+
+ passEncoder.executeBundles([renderEncoder.finish()]);
+ passEncoder.end();
+ t.device.queue.submit([encoder.finish()]);
+ t.verifyDataTwoValidValues(buffer, 1, 2);
+});
+
+g.test('two_dispatches_in_the_same_compute_pass').
+desc(
+ `Test write-after-write operations in the same compute pass. The first write will write 1 into
+ a storage buffer. The second write will write 2 into the same buffer in the same pass. Expected
+ data in buffer is 2.`
+).
+fn(async (t) => {
+ const buffer = await t.createBufferWithValue(0);
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+
+ for (let i = 0; i < 2; ++i) {
+ const pipeline = t.createStorageWriteComputePipeline(i + 1);
+ const bindGroup = t.createBindGroup(pipeline, buffer);
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ }
+
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+ t.verifyData(buffer, 2);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/operation_context_helper.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/operation_context_helper.js
new file mode 100644
index 0000000000..f3ddfa12bf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/operation_context_helper.js
@@ -0,0 +1,330 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../../common/util/util.js';
+
+/**
+ * Boundary between the first operation, and the second operation.
+ */
+export const kOperationBoundaries = [
+'queue-op', // Operations are performed in different queue operations (submit, writeTexture).
+'command-buffer', // Operations are in different command buffers.
+'pass', // Operations are in different passes.
+'execute-bundles', // Operations are in different executeBundles(...) calls
+'render-bundle', // Operations are in different render bundles.
+'dispatch', // Operations are in different dispatches.
+'draw' // Operations are in different draws.
+];
+
+
+/**
+ * Context a particular operation is permitted in.
+ * These contexts should be sorted such that the first is the most top-level
+ * context, and the last is most nested (inside a render bundle, in a render pass, ...).
+ */
+export const kOperationContexts = [
+'queue', // Operation occurs on the GPUQueue object
+'command-encoder', // Operation may be encoded in a GPUCommandEncoder.
+'compute-pass-encoder', // Operation may be encoded in a GPUComputePassEncoder.
+'render-pass-encoder', // Operation may be encoded in a GPURenderPassEncoder.
+'render-bundle-encoder' // Operation may be encoded in a GPURenderBundleEncoder.
+];
+
+
+
+
+
+
+
+function combineContexts(
+as,
+bs)
+{
+ const result = [];
+ for (const a of as) {
+ for (const b of bs) {
+ result.push([a, b]);
+ }
+ }
+ return result;
+}
+
+const queueContexts = combineContexts(kOperationContexts, kOperationContexts);
+const commandBufferContexts = combineContexts(
+ kOperationContexts.filter((c) => c !== 'queue'),
+ kOperationContexts.filter((c) => c !== 'queue')
+);
+
+/**
+ * Mapping of OperationBoundary => to a set of OperationContext pairs.
+ * The boundary is capable of separating operations in those two contexts.
+ */
+export const kBoundaryInfo =
+
+{
+ 'queue-op': {
+ contexts: queueContexts
+ },
+ 'command-buffer': {
+ contexts: commandBufferContexts
+ },
+ pass: {
+ contexts: [
+ ['compute-pass-encoder', 'compute-pass-encoder'],
+ ['compute-pass-encoder', 'render-pass-encoder'],
+ ['render-pass-encoder', 'compute-pass-encoder'],
+ ['render-pass-encoder', 'render-pass-encoder'],
+ ['render-bundle-encoder', 'render-pass-encoder'],
+ ['render-pass-encoder', 'render-bundle-encoder'],
+ ['render-bundle-encoder', 'render-bundle-encoder']]
+
+ },
+ 'execute-bundles': {
+ contexts: [['render-bundle-encoder', 'render-bundle-encoder']]
+ },
+ 'render-bundle': {
+ contexts: [
+ ['render-bundle-encoder', 'render-pass-encoder'],
+ ['render-pass-encoder', 'render-bundle-encoder'],
+ ['render-bundle-encoder', 'render-bundle-encoder']]
+
+ },
+ dispatch: {
+ contexts: [['compute-pass-encoder', 'compute-pass-encoder']]
+ },
+ draw: {
+ contexts: [
+ ['render-pass-encoder', 'render-pass-encoder'],
+ ['render-bundle-encoder', 'render-pass-encoder'],
+ ['render-pass-encoder', 'render-bundle-encoder']]
+
+ }
+};
+
+export class OperationContextHelper {
+ // We start at the queue context which is top-level.
+ currentContext = 'queue';
+
+ // Set based on the current context.
+
+
+
+
+
+
+
+
+
+ commandBuffers = [];
+ renderBundles = [];
+
+ kTextureSize = [4, 4];
+ kTextureFormat = 'rgba8unorm';
+
+ constructor(t) {
+ this.t = t;
+ this.device = t.device;
+ this.queue = t.device.queue;
+ }
+
+ // Ensure that all encoded commands are finished and submitted.
+ ensureSubmit() {
+ this.ensureContext('queue');
+ this.flushCommandBuffers();
+ }
+
+ popContext() {
+ switch (this.currentContext) {
+ case 'queue':
+ unreachable();
+ break;
+ case 'command-encoder':{
+ assert(this.commandEncoder !== undefined);
+ const commandBuffer = this.commandEncoder.finish();
+ this.commandEncoder = undefined;
+ this.currentContext = 'queue';
+ return commandBuffer;
+ }
+ case 'compute-pass-encoder':
+ assert(this.computePassEncoder !== undefined);
+ this.computePassEncoder.end();
+ this.computePassEncoder = undefined;
+ this.currentContext = 'command-encoder';
+ break;
+ case 'render-pass-encoder':
+ assert(this.renderPassEncoder !== undefined);
+ this.renderPassEncoder.end();
+ this.renderPassEncoder = undefined;
+ this.currentContext = 'command-encoder';
+ break;
+ case 'render-bundle-encoder':{
+ assert(this.renderBundleEncoder !== undefined);
+ const renderBundle = this.renderBundleEncoder.finish();
+ this.renderBundleEncoder = undefined;
+ this.currentContext = 'render-pass-encoder';
+ return renderBundle;
+ }
+ }
+ return null;
+ }
+
+ makeDummyAttachment() {
+ const texture = this.t.trackForCleanup(
+ this.device.createTexture({
+ format: this.kTextureFormat,
+ size: this.kTextureSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+ return {
+ view: texture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ };
+ }
+
+ ensureContext(context) {
+ // Find the common ancestor. So we can transition from currentContext -> context.
+ const ancestorContext =
+ kOperationContexts[
+ Math.min(
+ kOperationContexts.indexOf(context),
+ kOperationContexts.indexOf(this.currentContext)
+ )];
+
+
+ // Pop the context until we're at the common ancestor.
+ while (this.currentContext !== ancestorContext) {
+ // About to pop the render pass encoder. Execute any outstanding render bundles.
+ if (this.currentContext === 'render-pass-encoder') {
+ this.flushRenderBundles();
+ }
+
+ const result = this.popContext();
+ if (result) {
+ if (result instanceof GPURenderBundle) {
+ this.renderBundles.push(result);
+ } else {
+ this.commandBuffers.push(result);
+ }
+ }
+ }
+
+ if (this.currentContext === context) {
+ return;
+ }
+
+ switch (context) {
+ case 'queue':
+ unreachable();
+ break;
+ case 'command-encoder':
+ assert(this.currentContext === 'queue');
+ this.commandEncoder = this.device.createCommandEncoder();
+ break;
+ case 'compute-pass-encoder':
+ switch (this.currentContext) {
+ case 'queue':
+ this.commandEncoder = this.device.createCommandEncoder();
+ // fallthrough
+ case 'command-encoder':
+ assert(this.commandEncoder !== undefined);
+ this.computePassEncoder = this.commandEncoder.beginComputePass();
+ break;
+ case 'compute-pass-encoder':
+ case 'render-bundle-encoder':
+ case 'render-pass-encoder':
+ unreachable();
+ }
+ break;
+ case 'render-pass-encoder':
+ switch (this.currentContext) {
+ case 'queue':
+ this.commandEncoder = this.device.createCommandEncoder();
+ // fallthrough
+ case 'command-encoder':
+ assert(this.commandEncoder !== undefined);
+ this.renderPassEncoder = this.commandEncoder.beginRenderPass({
+ colorAttachments: [this.makeDummyAttachment()]
+ });
+ break;
+ case 'render-pass-encoder':
+ case 'render-bundle-encoder':
+ case 'compute-pass-encoder':
+ unreachable();
+ }
+ break;
+ case 'render-bundle-encoder':
+ switch (this.currentContext) {
+ case 'queue':
+ this.commandEncoder = this.device.createCommandEncoder();
+ // fallthrough
+ case 'command-encoder':
+ assert(this.commandEncoder !== undefined);
+ this.renderPassEncoder = this.commandEncoder.beginRenderPass({
+ colorAttachments: [this.makeDummyAttachment()]
+ });
+ // fallthrough
+ case 'render-pass-encoder':
+ this.renderBundleEncoder = this.device.createRenderBundleEncoder({
+ colorFormats: [this.kTextureFormat]
+ });
+ break;
+ case 'render-bundle-encoder':
+ case 'compute-pass-encoder':
+ unreachable();
+ }
+ break;
+ }
+ this.currentContext = context;
+ }
+
+ flushRenderBundles() {
+ assert(this.renderPassEncoder !== undefined);
+ if (this.renderBundles.length) {
+ this.renderPassEncoder.executeBundles(this.renderBundles);
+ this.renderBundles = [];
+ }
+ }
+
+ flushCommandBuffers() {
+ if (this.commandBuffers.length) {
+ this.queue.submit(this.commandBuffers);
+ this.commandBuffers = [];
+ }
+ }
+
+ ensureBoundary(boundary) {
+ switch (boundary) {
+ case 'command-buffer':
+ this.ensureContext('queue');
+ break;
+ case 'queue-op':
+ this.ensureContext('queue');
+ // Submit any GPUCommandBuffers so the next one is in a separate submit.
+ this.flushCommandBuffers();
+ break;
+ case 'dispatch':
+ // Nothing to do to separate dispatches.
+ assert(this.currentContext === 'compute-pass-encoder');
+ break;
+ case 'draw':
+ // Nothing to do to separate draws.
+ assert(
+ this.currentContext === 'render-pass-encoder' ||
+ this.currentContext === 'render-bundle-encoder'
+ );
+ break;
+ case 'pass':
+ this.ensureContext('command-encoder');
+ break;
+ case 'render-bundle':
+ this.ensureContext('render-pass-encoder');
+ break;
+ case 'execute-bundles':
+ this.ensureContext('render-pass-encoder');
+ // Execute any GPURenderBundles so the next one is in a separate executeBundles.
+ this.flushRenderBundles();
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/same_subresource.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/same_subresource.spec.js
new file mode 100644
index 0000000000..ca575db19b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/same_subresource.spec.js
@@ -0,0 +1,709 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Memory Synchronization Tests for Texture: read before write, read after write, and write after write to the same subresource.
+
+- TODO: Test synchronization between multiple queues.
+- TODO: Test depth/stencil attachments.
+- TODO: Use non-solid-color texture contents [2]
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { assert, memcpy, unreachable } from '../../../../../common/util/util.js';
+
+import { GPUTest } from '../../../../gpu_test.js';
+import { align } from '../../../../util/math.js';
+import { getTextureCopyLayout } from '../../../../util/texture/layout.js';
+import {
+ kTexelRepresentationInfo } from
+
+'../../../../util/texture/texel_data.js';
+import {
+ kOperationBoundaries,
+
+ kBoundaryInfo,
+ OperationContextHelper } from
+'../operation_context_helper.js';
+
+import {
+ kAllReadOps,
+ kAllWriteOps,
+ checkOpsValidForContext,
+
+ kOpInfo } from
+'./texture_sync_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const fullscreenQuadWGSL = `
+ struct VertexOutput {
+ @builtin(position) Position : vec4<f32>
+ };
+
+ @vertex fn vert_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;
+ }
+`;
+
+class TextureSyncTestHelper extends OperationContextHelper {
+
+
+ kTextureSize = [4, 4];
+ kTextureFormat = 'rgba8unorm';
+
+ constructor(
+ t,
+ textureCreationParams)
+
+
+ {
+ super(t);
+ this.texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: this.kTextureSize,
+ format: this.kTextureFormat,
+ ...textureCreationParams
+ })
+ );
+ }
+
+ /**
+ * Perform a read operation on the test texture.
+ * @return GPUTexture copy containing the contents.
+ */
+ performReadOp({ op, in: context }) {
+ this.ensureContext(context);
+ switch (op) {
+ case 't2t-copy':{
+ const texture = this.t.trackForCleanup(
+ this.device.createTexture({
+ size: this.kTextureSize,
+ format: this.kTextureFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ })
+ );
+
+ assert(this.commandEncoder !== undefined);
+ this.commandEncoder.copyTextureToTexture(
+ {
+ texture: this.texture
+ },
+ { texture },
+ this.kTextureSize
+ );
+ return texture;
+ }
+ case 't2b-copy':{
+ const { byteLength, bytesPerRow } = getTextureCopyLayout(this.kTextureFormat, '2d', [
+ ...this.kTextureSize,
+ 1]
+ );
+ const buffer = this.t.trackForCleanup(
+ this.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ })
+ );
+
+ const texture = this.t.trackForCleanup(
+ this.device.createTexture({
+ size: this.kTextureSize,
+ format: this.kTextureFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ })
+ );
+
+ assert(this.commandEncoder !== undefined);
+ this.commandEncoder.copyTextureToBuffer(
+ {
+ texture: this.texture
+ },
+ { buffer, bytesPerRow },
+ this.kTextureSize
+ );
+ this.commandEncoder.copyBufferToTexture(
+ { buffer, bytesPerRow },
+ { texture },
+ this.kTextureSize
+ );
+ return texture;
+ }
+ case 'sample':{
+ const texture = this.t.trackForCleanup(
+ this.device.createTexture({
+ size: this.kTextureSize,
+ format: this.kTextureFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING
+ })
+ );
+
+ const bindGroupLayout = this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
+ texture: {
+ sampleType: 'unfilterable-float'
+ }
+ },
+ {
+ binding: 1,
+ visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
+ storageTexture: {
+ access: 'write-only',
+ format: this.kTextureFormat
+ }
+ }]
+
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: this.texture.createView()
+ },
+ {
+ binding: 1,
+ resource: texture.createView()
+ }]
+
+ });
+
+ switch (context) {
+ case 'render-pass-encoder':
+ case 'render-bundle-encoder':{
+ const module = this.device.createShaderModule({
+ code: `${fullscreenQuadWGSL}
+
+ @group(0) @binding(0) var inputTex: texture_2d<f32>;
+ @group(0) @binding(1) var outputTex: texture_storage_2d<rgba8unorm, write>;
+
+ @fragment fn frag_main(@builtin(position) fragCoord: vec4<f32>) -> @location(0) vec4<f32> {
+ let coord = vec2<i32>(fragCoord.xy);
+ textureStore(outputTex, coord, textureLoad(inputTex, coord, 0));
+ return vec4<f32>();
+ }
+ `
+ });
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ }),
+ vertex: {
+ module,
+ entryPoint: 'vert_main'
+ },
+ fragment: {
+ module,
+ entryPoint: 'frag_main',
+
+ // Unused attachment since we can't use textureStore in the vertex shader.
+ // Set writeMask to zero.
+ targets: [
+ {
+ format: this.kTextureFormat,
+ writeMask: 0
+ }]
+
+ }
+ });
+
+ switch (context) {
+ case 'render-bundle-encoder':
+ assert(this.renderBundleEncoder !== undefined);
+ this.renderBundleEncoder.setPipeline(renderPipeline);
+ this.renderBundleEncoder.setBindGroup(0, bindGroup);
+ this.renderBundleEncoder.draw(6);
+ break;
+ case 'render-pass-encoder':
+ assert(this.renderPassEncoder !== undefined);
+ this.renderPassEncoder.setPipeline(renderPipeline);
+ this.renderPassEncoder.setBindGroup(0, bindGroup);
+ this.renderPassEncoder.draw(6);
+ break;
+ }
+ break;
+ }
+ case 'compute-pass-encoder':{
+ const module = this.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var inputTex: texture_2d<f32>;
+ @group(0) @binding(1) var outputTex: texture_storage_2d<rgba8unorm, write>;
+
+ @compute @workgroup_size(8, 8)
+ fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
+ if (any(gid.xy >= vec2<u32>(textureDimensions(inputTex)))) {
+ return;
+ }
+ let coord = vec2<i32>(gid.xy);
+ textureStore(outputTex, coord, textureLoad(inputTex, coord, 0));
+ }
+ `
+ });
+ const computePipeline = this.device.createComputePipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ }),
+ compute: {
+ module,
+ entryPoint: 'main'
+ }
+ });
+
+ assert(this.computePassEncoder !== undefined);
+ this.computePassEncoder.setPipeline(computePipeline);
+ this.computePassEncoder.setBindGroup(0, bindGroup);
+ this.computePassEncoder.dispatchWorkgroups(
+ Math.ceil(this.kTextureSize[0] / 8),
+ Math.ceil(this.kTextureSize[1] / 8)
+ );
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ return texture;
+ }
+ case 'b2t-copy':
+ case 'attachment-resolve':
+ case 'attachment-store':
+ unreachable();
+ }
+ unreachable();
+ }
+
+ performWriteOp(
+ { op, in: context },
+ data)
+ {
+ this.ensureContext(context);
+ switch (op) {
+ case 'attachment-store':{
+ assert(this.commandEncoder !== undefined);
+ this.renderPassEncoder = this.commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: this.texture.createView(),
+ // [2] Use non-solid-color texture values
+ clearValue: [data.R ?? 0, data.G ?? 0, data.B ?? 0, data.A ?? 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ this.currentContext = 'render-pass-encoder';
+ break;
+ }
+ case 'write-texture':{
+ // [2] Use non-solid-color texture values
+ const rep = kTexelRepresentationInfo[this.kTextureFormat];
+ const texelData = rep.pack(rep.encode(data));
+ const numTexels = this.kTextureSize[0] * this.kTextureSize[1];
+ const fullTexelData = new ArrayBuffer(texelData.byteLength * numTexels);
+ for (let i = 0; i < numTexels; ++i) {
+ memcpy({ src: texelData }, { dst: fullTexelData, start: i * texelData.byteLength });
+ }
+
+ this.queue.writeTexture(
+ { texture: this.texture },
+ fullTexelData,
+ {
+ bytesPerRow: texelData.byteLength * this.kTextureSize[0]
+ },
+ this.kTextureSize
+ );
+ break;
+ }
+ case 't2t-copy':{
+ const texture = this.device.createTexture({
+ size: this.kTextureSize,
+ format: this.kTextureFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ // [2] Use non-solid-color texture values
+ const rep = kTexelRepresentationInfo[this.kTextureFormat];
+ const texelData = rep.pack(rep.encode(data));
+ const numTexels = this.kTextureSize[0] * this.kTextureSize[1];
+ const fullTexelData = new ArrayBuffer(texelData.byteLength * numTexels);
+ for (let i = 0; i < numTexels; ++i) {
+ memcpy({ src: texelData }, { dst: fullTexelData, start: i * texelData.byteLength });
+ }
+
+ this.queue.writeTexture(
+ { texture },
+ fullTexelData,
+ {
+ bytesPerRow: texelData.byteLength * this.kTextureSize[0]
+ },
+ this.kTextureSize
+ );
+
+ assert(this.commandEncoder !== undefined);
+ this.commandEncoder.copyTextureToTexture(
+ { texture },
+ { texture: this.texture },
+ this.kTextureSize
+ );
+ break;
+ }
+ case 'b2t-copy':{
+ // [2] Use non-solid-color texture values
+ const rep = kTexelRepresentationInfo[this.kTextureFormat];
+ const texelData = rep.pack(rep.encode(data));
+ const bytesPerRow = align(texelData.byteLength, 256);
+ const fullTexelData = new ArrayBuffer(bytesPerRow * this.kTextureSize[1]);
+ for (let i = 0; i < this.kTextureSize[1]; ++i) {
+ for (let j = 0; j < this.kTextureSize[0]; ++j) {
+ memcpy(
+ { src: texelData },
+ {
+ dst: fullTexelData,
+ start: i * bytesPerRow + j * texelData.byteLength
+ }
+ );
+ }
+ }
+
+ const buffer = this.t.trackForCleanup(
+ this.device.createBuffer({
+ size: fullTexelData.byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ })
+ );
+
+ this.queue.writeBuffer(buffer, 0, fullTexelData);
+
+ assert(this.commandEncoder !== undefined);
+ this.commandEncoder.copyBufferToTexture(
+ { buffer, bytesPerRow },
+ { texture: this.texture },
+ this.kTextureSize
+ );
+ break;
+ }
+ case 'attachment-resolve':{
+ assert(this.commandEncoder !== undefined);
+ const renderTarget = this.t.trackForCleanup(
+ this.device.createTexture({
+ format: this.kTextureFormat,
+ size: this.kTextureSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4
+ })
+ );
+ this.renderPassEncoder = this.commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ resolveTarget: this.texture.createView(),
+ // [2] Use non-solid-color texture values
+ clearValue: [data.R ?? 0, data.G ?? 0, data.B ?? 0, data.A ?? 0],
+ loadOp: 'clear',
+ storeOp: 'discard'
+ }]
+
+ });
+ this.currentContext = 'render-pass-encoder';
+ break;
+ }
+ case 'storage':{
+ const bindGroupLayout = this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
+ storageTexture: {
+ access: 'write-only',
+ format: this.kTextureFormat
+ }
+ }]
+
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: this.texture.createView()
+ }]
+
+ });
+
+ // [2] Use non-solid-color texture values
+ const storedValue = `vec4<f32>(${[data.R ?? 0, data.G ?? 0, data.B ?? 0, data.A ?? 0].
+ map((x) => x.toFixed(5)).
+ join(', ')})`;
+
+ switch (context) {
+ case 'render-pass-encoder':
+ case 'render-bundle-encoder':{
+ const module = this.device.createShaderModule({
+ code: `${fullscreenQuadWGSL}
+
+ @group(0) @binding(0) var outputTex: texture_storage_2d<rgba8unorm, write>;
+
+ @fragment fn frag_main(@builtin(position) fragCoord: vec4<f32>) -> @location(0) vec4<f32> {
+ textureStore(outputTex, vec2<i32>(fragCoord.xy), ${storedValue});
+ return vec4<f32>();
+ }
+ `
+ });
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ }),
+ vertex: {
+ module,
+ entryPoint: 'vert_main'
+ },
+ fragment: {
+ module,
+ entryPoint: 'frag_main',
+
+ // Unused attachment since we can't use textureStore in the vertex shader.
+ // Set writeMask to zero.
+ targets: [
+ {
+ format: this.kTextureFormat,
+ writeMask: 0
+ }]
+
+ }
+ });
+
+ switch (context) {
+ case 'render-bundle-encoder':
+ assert(this.renderBundleEncoder !== undefined);
+ this.renderBundleEncoder.setPipeline(renderPipeline);
+ this.renderBundleEncoder.setBindGroup(0, bindGroup);
+ this.renderBundleEncoder.draw(6);
+ break;
+ case 'render-pass-encoder':
+ assert(this.renderPassEncoder !== undefined);
+ this.renderPassEncoder.setPipeline(renderPipeline);
+ this.renderPassEncoder.setBindGroup(0, bindGroup);
+ this.renderPassEncoder.draw(6);
+ break;
+ }
+ break;
+ }
+ case 'compute-pass-encoder':{
+ const module = this.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var outputTex: texture_storage_2d<rgba8unorm, write>;
+
+ @compute @workgroup_size(8, 8)
+ fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
+ if (any(gid.xy >= vec2<u32>(textureDimensions(outputTex)))) {
+ return;
+ }
+ let coord = vec2<i32>(gid.xy);
+ textureStore(outputTex, coord, ${storedValue});
+ }
+ `
+ });
+ const computePipeline = this.device.createComputePipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ }),
+ compute: {
+ module,
+ entryPoint: 'main'
+ }
+ });
+
+ assert(this.computePassEncoder !== undefined);
+ this.computePassEncoder.setPipeline(computePipeline);
+ this.computePassEncoder.setBindGroup(0, bindGroup);
+ this.computePassEncoder.dispatchWorkgroups(
+ Math.ceil(this.kTextureSize[0] / 8),
+ Math.ceil(this.kTextureSize[1] / 8)
+ );
+ break;
+ }
+ default:
+ unreachable();
+ }
+ break;
+ }
+ case 't2b-copy':
+ case 'sample':
+ unreachable();
+ }
+ }
+}
+
+g.test('rw').
+desc(
+ `
+ Perform a 'read' operations on a texture subresource, followed by a 'write' operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The read should not see the contents written by the subsequent write.`
+).
+params((u) =>
+u.
+combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const read of kAllReadOps) {
+ for (const write of kAllWriteOps) {
+ if (checkOpsValidForContext([read, write], _context)) {
+ yield {
+ read: { op: read, in: _context[0] },
+ write: { op: write, in: _context[1] }
+ };
+ }
+ }
+ }
+})
+).
+fn((t) => {
+ const helper = new TextureSyncTestHelper(t, {
+ usage:
+ GPUTextureUsage.COPY_DST |
+ kOpInfo[t.params.read.op].readUsage |
+ kOpInfo[t.params.write.op].writeUsage
+ });
+ // [2] Use non-solid-color texture value.
+ const texelValue1 = { R: 0, G: 1, B: 0, A: 1 };
+ const texelValue2 = { R: 1, G: 0, B: 0, A: 1 };
+
+ // Initialize the texture with something.
+ helper.performWriteOp({ op: 'write-texture', in: 'queue' }, texelValue1);
+ const readbackTexture = helper.performReadOp(t.params.read);
+ helper.ensureBoundary(t.params.boundary);
+ helper.performWriteOp(t.params.write, texelValue2);
+ helper.ensureSubmit();
+
+ // Contents should be the first value written, not the second.
+ t.expectSingleColor(readbackTexture, helper.kTextureFormat, {
+ size: [...helper.kTextureSize, 1],
+ exp: texelValue1
+ });
+});
+
+g.test('wr').
+desc(
+ `
+ Perform a 'write' operation on a texture subresource, followed by a 'read' operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The read should see exactly the contents written by the previous write.
+
+ - TODO: Use non-solid-color texture contents [2]`
+).
+params((u) =>
+u.
+combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const read of kAllReadOps) {
+ for (const write of kAllWriteOps) {
+ if (checkOpsValidForContext([write, read], _context)) {
+ yield {
+ write: { op: write, in: _context[0] },
+ read: { op: read, in: _context[1] }
+ };
+ }
+ }
+ }
+})
+).
+fn((t) => {
+ const helper = new TextureSyncTestHelper(t, {
+ usage: kOpInfo[t.params.read.op].readUsage | kOpInfo[t.params.write.op].writeUsage
+ });
+ // [2] Use non-solid-color texture value.
+ const texelValue = { R: 0, G: 1, B: 0, A: 1 };
+
+ helper.performWriteOp(t.params.write, texelValue);
+ helper.ensureBoundary(t.params.boundary);
+ const readbackTexture = helper.performReadOp(t.params.read);
+ helper.ensureSubmit();
+
+ // Contents should be exactly the values written.
+ t.expectSingleColor(readbackTexture, helper.kTextureFormat, {
+ size: [...helper.kTextureSize, 1],
+ exp: texelValue
+ });
+});
+
+g.test('ww').
+desc(
+ `
+ Perform a 'first' write operation on a texture subresource, followed by a 'second' write operation.
+ Operations are separated by a 'boundary' (pass, encoder, queue-op, etc.).
+ Test that the results are synchronized.
+ The second write should overwrite the contents of the first.`
+).
+params((u) =>
+u.
+combine('boundary', kOperationBoundaries).
+expand('_context', (p) => kBoundaryInfo[p.boundary].contexts).
+expandWithParams(function* ({ _context }) {
+ for (const first of kAllWriteOps) {
+ for (const second of kAllWriteOps) {
+ if (checkOpsValidForContext([first, second], _context)) {
+ yield {
+ first: { op: first, in: _context[0] },
+ second: { op: second, in: _context[1] }
+ };
+ }
+ }
+ }
+})
+).
+fn((t) => {
+ const helper = new TextureSyncTestHelper(t, {
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ kOpInfo[t.params.first.op].writeUsage |
+ kOpInfo[t.params.second.op].writeUsage
+ });
+ // [2] Use non-solid-color texture value.
+ const texelValue1 = { R: 1, G: 0, B: 0, A: 1 };
+ const texelValue2 = { R: 0, G: 1, B: 0, A: 1 };
+
+ helper.performWriteOp(t.params.first, texelValue1);
+ helper.ensureBoundary(t.params.boundary);
+ helper.performWriteOp(t.params.second, texelValue2);
+ helper.ensureSubmit();
+
+ // Read back the contents so we can test the result.
+ const readbackTexture = helper.performReadOp({ op: 't2t-copy', in: 'command-encoder' });
+ helper.ensureSubmit();
+
+ // Contents should be the second value written.
+ t.expectSingleColor(readbackTexture, helper.kTextureFormat, {
+ size: [...helper.kTextureSize, 1],
+ exp: texelValue2
+ });
+});
+
+g.test('rw,single_pass,load_store').
+desc(
+ `
+ TODO: Test memory synchronization when loading from a texture subresource in a single pass and storing to it.`
+).
+unimplemented();
+
+g.test('rw,single_pass,load_resolve').
+desc(
+ `
+ TODO: Test memory synchronization when loading from a texture subresource in a single pass and resolving to it.`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/texture_sync_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/texture_sync_test.js
new file mode 100644
index 0000000000..f23af118bb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/memory_sync/texture/texture_sync_test.js
@@ -0,0 +1,124 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { GPUConst } from '../../../../constants.js';
+export const kAllWriteOps = [
+'write-texture',
+'b2t-copy',
+'t2t-copy',
+'storage',
+'attachment-store',
+'attachment-resolve'];
+
+
+
+export const kAllReadOps = ['t2b-copy', 't2t-copy', 'sample'];
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Mapping of Op to the OperationContext(s) it is valid in
+ */
+export const kOpInfo =
+
+{
+ 'write-texture': {
+ contexts: ['queue'],
+ readUsage: 0,
+ writeUsage: GPUConst.TextureUsage.COPY_DST
+ },
+ 'b2t-copy': {
+ contexts: ['command-encoder'],
+ readUsage: 0,
+ writeUsage: GPUConst.TextureUsage.COPY_DST
+ },
+ 't2t-copy': {
+ contexts: ['command-encoder'],
+ readUsage: GPUConst.TextureUsage.COPY_SRC,
+ writeUsage: GPUConst.TextureUsage.COPY_DST
+ },
+ 't2b-copy': {
+ contexts: ['command-encoder'],
+ readUsage: GPUConst.TextureUsage.COPY_SRC,
+ writeUsage: 0
+ },
+ storage: {
+ contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder'],
+ readUsage: 0,
+ writeUsage: GPUConst.TextureUsage.STORAGE
+ },
+ sample: {
+ contexts: ['compute-pass-encoder', 'render-pass-encoder', 'render-bundle-encoder'],
+ readUsage: GPUConst.TextureUsage.SAMPLED,
+ writeUsage: 0
+ },
+ 'attachment-store': {
+ contexts: ['command-encoder'],
+ readUsage: 0,
+ writeUsage: GPUConst.TextureUsage.RENDER_ATTACHMENT
+ },
+ 'attachment-resolve': {
+ contexts: ['command-encoder'],
+ readUsage: 0,
+ writeUsage: GPUConst.TextureUsage.RENDER_ATTACHMENT
+ }
+};
+
+export function checkOpsValidForContext(
+ops,
+context)
+{
+ const valid =
+ kOpInfo[ops[0]].contexts.includes(context[0]) && kOpInfo[ops[1]].contexts.includes(context[1]);
+ if (!valid) return false;
+
+ if (
+ context[0] === 'render-bundle-encoder' ||
+ context[0] === 'render-pass-encoder' ||
+ context[1] === 'render-bundle-encoder' ||
+ context[1] === 'render-pass-encoder')
+ {
+ // In a render pass, it is invalid to use a resource as both writable and another usage.
+ // Also, for storage+storage usage, the application is opting into racy behavior.
+ // The storage+storage case is also skipped as the results cannot be reliably tested.
+ const checkImpl = (op1, op2) => {
+ switch (op1) {
+ case 'attachment-resolve':
+ case 'attachment-store':
+ case 'storage':
+ switch (op2) {
+ case 'attachment-resolve':
+ case 'attachment-store':
+ case 'storage':
+ case 'sample':
+ // Write+other, or racy.
+ return false;
+ case 'b2t-copy':
+ case 't2b-copy':
+ case 't2t-copy':
+ case 'write-texture':
+ // These don't occur in a render pass.
+ return true;
+ }
+ break;
+ case 'b2t-copy':
+ case 'sample':
+ case 't2b-copy':
+ case 't2t-copy':
+ case 'write-texture':
+ // These are not write usages, or don't occur in a render pass.
+ break;
+ }
+ return true;
+ };
+ return checkImpl(ops[0], ops[1]) && checkImpl(ops[1], ops[0]);
+ }
+ return true;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/onSubmittedWorkDone.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/onSubmittedWorkDone.spec.js
new file mode 100644
index 0000000000..e6d6fae9b5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/onSubmittedWorkDone.spec.js
@@ -0,0 +1,56 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for the behavior of GPUQueue.onSubmittedWorkDone().
+
+Note that any promise timeouts will be detected by the framework.
+`;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);
+
+g.test('without_work').
+desc(`Await onSubmittedWorkDone once without having submitted any work.`).
+fn(async (t) => {
+ await t.queue.onSubmittedWorkDone();
+});
+
+g.test('with_work').
+desc(`Await onSubmittedWorkDone once after submitting some work (writeBuffer).`).
+fn(async (t) => {
+ const buffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST });
+ t.queue.writeBuffer(buffer, 0, new Uint8Array(4));
+ await t.queue.onSubmittedWorkDone();
+});
+
+g.test('many,serial').
+desc(`Await 1000 onSubmittedWorkDone calls in serial.`).
+fn(async (t) => {
+ for (let i = 0; i < 1000; ++i) {
+ await t.queue.onSubmittedWorkDone();
+ }
+});
+
+g.test('many,parallel').
+desc(`Await 1000 onSubmittedWorkDone calls in parallel with Promise.all().`).
+fn(async (t) => {
+ const promises = range(1000, () => t.queue.onSubmittedWorkDone());
+ await Promise.all(promises);
+});
+
+g.test('many,parallel_order').
+desc(`Issue 200 onSubmittedWorkDone calls and make sure they resolve in the right order.`).
+fn(async (t) => {
+ const promises = [];
+ let lastResolved = -1;
+ for (const i of range(200, (i) => i)) {
+ promises.push(
+ t.queue.onSubmittedWorkDone().then(() => {
+ t.expect(i === lastResolved + 1);
+ lastResolved++;
+ })
+ );
+ }
+ await Promise.all(promises);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/pipeline/default_layout.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/pipeline/default_layout.spec.js
new file mode 100644
index 0000000000..1886c81b31
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/pipeline/default_layout.spec.js
@@ -0,0 +1,27 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for default pipeline layouts.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('getBindGroupLayout_js_object').
+desc(
+ `Test that getBindGroupLayout returns [TODO: the same or a different, needs spec] object
+each time.`
+).
+unimplemented();
+
+g.test('incompatible_with_explicit').
+desc(`Test that default bind group layouts are never compatible with explicitly created ones.`).
+unimplemented();
+
+g.test('layout').
+desc(
+ `Test that bind group layouts of the default pipeline layout are correct by passing various
+shaders and then checking their computed bind group layouts are compatible with particular bind
+groups.`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/queue/writeBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/queue/writeBuffer.spec.js
new file mode 100644
index 0000000000..261b756a7b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/queue/writeBuffer.spec.js
@@ -0,0 +1,235 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = 'Operation tests for GPUQueue.writeBuffer()';import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { memcpy, range } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { align } from '../../../util/math.js';
+
+const kTypedArrays = [
+'Uint8Array',
+'Uint16Array',
+'Uint32Array',
+'Int8Array',
+'Int16Array',
+'Int32Array',
+'Float32Array',
+'Float64Array'];
+
+
+
+
+
+
+
+
+
+
+
+class F extends GPUTest {
+ calculateRequiredBufferSize(writes) {
+ let bufferSize = 0;
+ // Calculate size of final buffer
+ for (const { bufferOffset, data, arrayType, useArrayBuffer, dataOffset, dataSize } of writes) {
+ const TypedArrayConstructor = globalThis[arrayType];
+
+ // When passing data as an ArrayBuffer, dataOffset and dataSize use byte instead of number of
+ // elements. bytesPerElement is used to convert dataOffset and dataSize from elements to bytes
+ // when useArrayBuffer === false.
+ const bytesPerElement = useArrayBuffer ? 1 : TypedArrayConstructor.BYTES_PER_ELEMENT;
+
+ // Calculate the number of bytes written to the buffer. data is always an array of elements.
+ let bytesWritten =
+ data.length * TypedArrayConstructor.BYTES_PER_ELEMENT - (dataOffset || 0) * bytesPerElement;
+
+ if (dataSize) {
+ // When defined, dataSize clamps the number of bytes written
+ bytesWritten = Math.min(bytesWritten, dataSize * bytesPerElement);
+ }
+
+ // The minimum buffer size required for the write to succeed is the number of bytes written +
+ // the bufferOffset
+ const requiredBufferSize = bufferOffset + bytesWritten;
+
+ // Find the largest required size by all writes
+ bufferSize = Math.max(bufferSize, requiredBufferSize);
+ }
+ // writeBuffer requires buffers to be a multiple of 4
+ return align(bufferSize, 4);
+ }
+
+ testWriteBuffer(...writes) {
+ const bufferSize = this.calculateRequiredBufferSize(writes);
+
+ // Initialize buffer to non-zero data (0xff) for easier debug.
+ const expectedData = new Uint8Array(bufferSize).fill(0xff);
+
+ const buffer = this.makeBufferWithContents(
+ expectedData,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+
+ for (const { bufferOffset, data, arrayType, useArrayBuffer, dataOffset, dataSize } of writes) {
+ const TypedArrayConstructor = globalThis[arrayType];
+ const writeData = new TypedArrayConstructor(data);
+ const writeSrc = useArrayBuffer ? writeData.buffer : writeData;
+ this.queue.writeBuffer(buffer, bufferOffset, writeSrc, dataOffset, dataSize);
+ memcpy(
+ { src: writeSrc, start: dataOffset, length: dataSize },
+ { dst: expectedData, start: bufferOffset }
+ );
+ }
+
+ this.debug(`expectedData: [${expectedData.join(', ')}]`);
+ this.expectGPUBufferValuesEqual(buffer, expectedData);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kTestData = range(16, (i) => i);
+
+g.test('array_types').
+desc('Tests that writeBuffer correctly handles different TypedArrays and ArrayBuffer.').
+params((u) =>
+u //
+.combine('arrayType', kTypedArrays).
+combine('useArrayBuffer', [false, true])
+).
+fn((t) => {
+ const { arrayType, useArrayBuffer } = t.params;
+ const dataOffset = 1;
+ const dataSize = 8;
+ t.testWriteBuffer({
+ bufferOffset: 0,
+ arrayType,
+ data: kTestData,
+ dataOffset,
+ dataSize,
+ useArrayBuffer
+ });
+});
+
+g.test('multiple_writes_at_different_offsets_and_sizes').
+desc(
+ `
+Tests that writeBuffer currently handles different offsets and writes. This includes:
+- Non-overlapping TypedArrays and ArrayLists
+- Overlapping TypedArrays and ArrayLists
+- Writing zero data
+- Writing on zero sized buffers
+- Unaligned source
+- Multiple overlapping writes with decreasing sizes
+ `
+).
+paramsSubcasesOnly([
+{
+ // Concatenate 2 Uint32Arrays
+ writes: [
+ {
+ bufferOffset: 0,
+ data: kTestData,
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false,
+ dataOffset: 2,
+ dataSize: 2
+ }, // [2, 3]
+ {
+ bufferOffset: 2 * Uint32Array.BYTES_PER_ELEMENT,
+ data: kTestData,
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false,
+ dataOffset: 0,
+ dataSize: 2
+ } // [0, 1]
+ ] // Expected [2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
+},
+{
+ // Concatenate 2 Uint8Arrays
+ writes: [
+ { bufferOffset: 0, data: [0, 1, 2, 3], arrayType: 'Uint8Array', useArrayBuffer: false },
+ { bufferOffset: 4, data: [4, 5, 6, 7], arrayType: 'Uint8Array', useArrayBuffer: false }]
+ // Expected [0, 1, 2, 3, 4, 5, 6, 7]
+},
+{
+ // Overlap in the middle
+ writes: [
+ { bufferOffset: 0, data: kTestData, arrayType: 'Uint8Array', useArrayBuffer: false },
+ { bufferOffset: 4, data: [0], arrayType: 'Uint32Array', useArrayBuffer: false }]
+ // Expected [0, 1, 2, 3, 0, 0 ,0 ,0, 8, 9, 10, 11, 12, 13, 14, 15]
+},
+{
+ // Overlapping arrayLists
+ writes: [
+ {
+ bufferOffset: 0,
+ data: kTestData,
+ arrayType: 'Uint32Array',
+ useArrayBuffer: true,
+ dataOffset: 2,
+ dataSize: 4 * Uint32Array.BYTES_PER_ELEMENT
+ },
+ { bufferOffset: 4, data: [0x04030201], arrayType: 'Uint32Array', useArrayBuffer: true }]
+ // Expected [0, 0, 1, 0, 1, 2, 3, 4, 0, 0, 3, 0, 0, 0, 4, 0]
+},
+{
+ // Write over with empty buffer
+ writes: [
+ { bufferOffset: 0, data: kTestData, arrayType: 'Uint8Array', useArrayBuffer: false },
+ { bufferOffset: 0, data: [], arrayType: 'Uint8Array', useArrayBuffer: false }]
+ // Expected [0, 1, 2, 3, 4, 5 ,6 ,7, 8, 9, 10, 11, 12, 13, 14, 15]
+},
+{
+ // Zero buffer
+ writes: [{ bufferOffset: 0, data: [], arrayType: 'Uint8Array', useArrayBuffer: false }]
+}, // Expected []
+{
+ // Unaligned source
+ writes: [
+ {
+ bufferOffset: 0,
+ data: [0x77, ...kTestData],
+ arrayType: 'Uint8Array',
+ useArrayBuffer: false,
+ dataOffset: 1
+ }]
+ // Expected [0, 1, 2, 3, 4, 5 ,6 ,7, 8, 9, 10, 11, 12, 13, 14, 15]
+},
+{
+ // Multiple overlapping writes
+ writes: [
+ {
+ bufferOffset: 0,
+ data: [0x05050505, 0x05050505, 0x05050505, 0x05050505, 0x05050505],
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false
+ },
+ {
+ bufferOffset: 0,
+ data: [0x04040404, 0x04040404, 0x04040404, 0x04040404],
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false
+ },
+ {
+ bufferOffset: 0,
+ data: [0x03030303, 0x03030303, 0x03030303],
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false
+ },
+ {
+ bufferOffset: 0,
+ data: [0x02020202, 0x02020202],
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false
+ },
+ {
+ bufferOffset: 0,
+ data: [0x01010101],
+ arrayType: 'Uint32Array',
+ useArrayBuffer: false
+ }]
+ // Expected [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]
+}]
+).
+fn((t) => {
+ t.testWriteBuffer(...t.params.writes);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/reflection.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/reflection.spec.js
new file mode 100644
index 0000000000..da9d90b819
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/reflection.spec.js
@@ -0,0 +1,137 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests that object attributes which reflect the object's creation properties are properly set.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUConst } from '../../constants.js';
+import { GPUTest } from '../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+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
+}]
+)
+).
+fn((t) => {
+ const { descriptor } = t.params;
+
+ t.expectValidationError(() => {
+ const buffer = t.device.createBuffer(descriptor);
+
+ t.expect(buffer.size === descriptor.size);
+ t.expect(buffer.usage === descriptor.usage);
+ }, descriptor.invalid === true);
+});
+
+g.test('texture_reflection_attributes').
+desc(`For every texture attribute, the corresponding descriptor value is carried over.`).
+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
+}]
+)
+).
+fn((t) => {
+ const { descriptor } = t.params;
+
+ let width;
+ let height;
+ let depthOrArrayLayers;
+ if (Array.isArray(descriptor.size)) {
+ width = descriptor.size[0];
+ height = descriptor.size[1] || 1;
+ depthOrArrayLayers = descriptor.size[2] || 1;
+ } else {
+ width = descriptor.size.width;
+ height = descriptor.size.height || 1;
+ depthOrArrayLayers = descriptor.size.depthOrArrayLayers || 1;
+ }
+
+ t.expectValidationError(() => {
+ const texture = t.device.createTexture(descriptor);
+
+ t.expect(texture.width === width);
+ t.expect(texture.height === height);
+ t.expect(texture.depthOrArrayLayers === depthOrArrayLayers);
+ t.expect(texture.format === descriptor.format);
+ t.expect(texture.usage === descriptor.usage);
+ t.expect(texture.dimension === (descriptor.dimension || '2d'));
+ t.expect(texture.mipLevelCount === (descriptor.mipLevelCount || 1));
+ t.expect(texture.sampleCount === (descriptor.sampleCount || 1));
+ }, descriptor.invalid === true);
+});
+
+g.test('query_set_reflection_attributes').
+desc(`For every queue attribute, the corresponding descriptor value is carried over.`).
+paramsSubcasesOnly((u) =>
+u.combine('descriptor', [
+{ type: 'occlusion', count: 4 },
+{ type: 'occlusion', count: 16 },
+{ type: 'occlusion', count: 8193, invalid: true }]
+)
+).
+fn((t) => {
+ const { descriptor } = t.params;
+
+ t.expectValidationError(() => {
+ const querySet = t.device.createQuerySet(descriptor);
+
+ t.expect(querySet.type === descriptor.type);
+ t.expect(querySet.count === descriptor.count);
+ }, descriptor.invalid === true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/clear_value.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/clear_value.spec.js
new file mode 100644
index 0000000000..5c127533ba
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/clear_value.spec.js
@@ -0,0 +1,188 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for render pass clear values.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import {
+ kTextureFormatInfo,
+ kDepthStencilFormats,
+ depthStencilFormatAspectSize } from
+'../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stored').
+desc(`Test render pass clear values are stored at the end of an empty pass.`).
+unimplemented();
+
+g.test('loaded').
+desc(
+ `Test render pass clear values are visible during the pass by doing some trivial blending
+with the attachment (e.g. add [0,0,0,0] to the color and verify the stored result).`
+).
+unimplemented();
+
+g.test('srgb').
+desc(
+ `Test that clear values on '-srgb' type attachments are interpreted as unencoded (linear),
+not decoded from srgb to linear.`
+).
+unimplemented();
+
+g.test('layout').
+desc(
+ `Test that bind group layouts of the default pipeline layout are correct by passing various
+shaders and then checking their computed bind group layouts are compatible with particular bind
+groups.`
+).
+unimplemented();
+
+g.test('stencil_clear_value').
+desc(
+ `Test that when stencilLoadOp is "clear", the stencil aspect should be correctly cleared by
+ GPURenderPassDepthStencilAttachment.stencilClearValue, which will be converted to the type of
+ the stencil aspect of view by taking the same number of LSBs as the number of bits in the
+ stencil aspect of one texel block of view.`
+).
+params((u) =>
+u.
+combine('stencilFormat', kDepthStencilFormats).
+combine('stencilClearValue', [0, 1, 0xff, 0x100 + 2, 0x10000 + 3]).
+combine('applyStencilClearValueAsStencilReferenceValue', [true, false]).
+filter((t) => !!kTextureFormatInfo[t.stencilFormat].stencil)
+).
+beforeAllSubcases((t) => {
+ const { stencilFormat } = t.params;
+ const info = kTextureFormatInfo[stencilFormat];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { stencilFormat, stencilClearValue, applyStencilClearValueAsStencilReferenceValue } =
+ t.params;
+
+ const kSize = [1, 1, 1];
+ const colorFormat = 'rgba8unorm';
+ const stencilTexture = t.device.createTexture({
+ format: stencilFormat,
+ size: kSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ const colorTexture = t.device.createTexture({
+ format: colorFormat,
+ size: kSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ const renderPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)-> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 6> = 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));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorFormat }]
+ },
+ depthStencil: {
+ format: stencilFormat,
+ depthCompare: 'always',
+ depthWriteEnabled: false,
+ stencilFront: {
+ compare: 'equal'
+ },
+ stencilBack: {
+ compare: 'equal'
+ }
+ },
+ primitive: {
+ topology: 'triangle-list'
+ }
+ });
+
+ const stencilAspectSizeInBytes = depthStencilFormatAspectSize(stencilFormat, 'stencil-only');
+ assert(stencilAspectSizeInBytes > 0);
+ const expectedStencilValue = stencilClearValue & (stencilAspectSizeInBytes << 8) - 1;
+
+ // StencilReference used in setStencilReference will also be masked to the lowest valid bits, so
+ // no matter what we set in the rest high bits that will be masked out (different or same
+ // between stencilClearValue and stencilReference), the test will pass if and only if the valid
+ // lowest bits are the same.
+ const stencilReference = applyStencilClearValueAsStencilReferenceValue ?
+ stencilClearValue :
+ expectedStencilValue;
+
+ const encoder = t.device.createCommandEncoder();
+
+ const depthStencilAttachment = {
+ view: stencilTexture.createView(),
+ depthClearValue: 0,
+ stencilLoadOp: 'clear',
+ stencilStoreOp: 'store',
+ stencilClearValue
+ };
+ if (kTextureFormatInfo[stencilFormat].depth) {
+ depthStencilAttachment.depthClearValue = 0;
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'store';
+ }
+ const renderPassEncoder = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ clearValue: [1, 0, 0, 1]
+ }],
+
+ depthStencilAttachment
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setStencilReference(stencilReference);
+ renderPassEncoder.draw(6);
+ renderPassEncoder.end();
+
+ const destinationBuffer = t.device.createBuffer({
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ size: 4
+ });
+ t.trackForCleanup(destinationBuffer);
+ encoder.copyTextureToBuffer(
+ {
+ texture: stencilTexture,
+ aspect: 'stencil-only'
+ },
+ {
+ buffer: destinationBuffer
+ },
+ [1, 1, 1]
+ );
+
+ t.queue.submit([encoder.finish()]);
+
+ t.expectSingleColor(colorTexture, colorFormat, {
+ size: [1, 1, 1],
+ exp: { R: 0, G: 1, B: 0, A: 1 }
+ });
+ t.expectGPUBufferValuesEqual(destinationBuffer, new Uint8Array([expectedStencilValue]));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/resolve.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/resolve.spec.js
new file mode 100644
index 0000000000..abcbe24bd8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/resolve.spec.js
@@ -0,0 +1,183 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `API Operation Tests for RenderPass StoreOp.
+Tests a render pass with a resolveTarget resolves correctly for many combinations of:
+ - number of color attachments, some with and some without a resolveTarget
+ - renderPass storeOp set to {'store', 'discard'}
+ - resolveTarget mip level {0, >0} (TODO?: different mip level from colorAttachment)
+ - resolveTarget {2d array layer, TODO: 3d slice} {0, >0} with {2d, TODO: 3d} resolveTarget
+ (TODO?: different z from colorAttachment)
+ - TODO: test all renderable color formats
+ - TODO: test that any not-resolved attachments are rendered to correctly.
+ - TODO: test different loadOps
+ - TODO?: resolveTarget mip level {0, >0} (TODO?: different mip level from colorAttachment)
+ - TODO?: resolveTarget {2d array layer, TODO: 3d slice} {0, >0} with {2d, TODO: 3d} resolveTarget
+ (different z from colorAttachment)
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+
+const kSlotsToResolve = [
+[0, 2],
+[1, 3],
+[0, 1, 2, 3]];
+
+
+const kSize = 4;
+const kFormat = 'rgba8unorm';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+g.test('render_pass_resolve').
+params((u) =>
+u.
+combine('storeOperation', ['discard', 'store']).
+beginSubcases().
+combine('numColorAttachments', [2, 4]).
+combine('slotsToResolve', kSlotsToResolve).
+combine('resolveTargetBaseMipLevel', [0, 1]).
+combine('resolveTargetBaseArrayLayer', [0, 1])
+).
+fn((t) => {
+ const targets = [];
+ for (let i = 0; i < t.params.numColorAttachments; i++) {
+ targets.push({ format: kFormat });
+ }
+
+ // These shaders will draw a white triangle into a texture. After draw, the top left
+ // half of the texture will be white, and the bottom right half will be unchanged. When this
+ // texture is resolved, there will be two distinct colors in each portion of the texture, as
+ // well as a line between the portions that contain the midpoint color due to the multisample
+ // resolve.
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>( 1.0, 1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ struct Output {
+ @location(0) fragColor0 : vec4<f32>,
+ @location(1) fragColor1 : vec4<f32>,
+ @location(2) fragColor2 : vec4<f32>,
+ @location(3) fragColor3 : vec4<f32>,
+ };
+
+ @fragment fn main() -> Output {
+ return Output(
+ vec4<f32>(1.0, 1.0, 1.0, 1.0),
+ vec4<f32>(1.0, 1.0, 1.0, 1.0),
+ vec4<f32>(1.0, 1.0, 1.0, 1.0),
+ vec4<f32>(1.0, 1.0, 1.0, 1.0)
+ );
+ }`
+ }),
+ entryPoint: 'main',
+ targets
+ },
+ primitive: { topology: 'triangle-list' },
+ multisample: { count: 4 }
+ });
+
+ const resolveTargets = [];
+ const renderPassColorAttachments = [];
+
+ // The resolve target must be the same size as the color attachment. If we're resolving to mip
+ // level 1, the resolve target base mip level should be 2x the color attachment size.
+ const kResolveTargetSize = kSize << t.params.resolveTargetBaseMipLevel;
+
+ for (let i = 0; i < t.params.numColorAttachments; i++) {
+ const colorAttachment = t.device.createTexture({
+ format: kFormat,
+ size: { width: kSize, height: kSize, depthOrArrayLayers: 1 },
+ sampleCount: 4,
+ mipLevelCount: 1,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ if (t.params.slotsToResolve.includes(i)) {
+ const colorAttachment = t.device.createTexture({
+ format: kFormat,
+ size: { width: kSize, height: kSize, depthOrArrayLayers: 1 },
+ sampleCount: 4,
+ mipLevelCount: 1,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const resolveTarget = t.device.createTexture({
+ format: kFormat,
+ size: {
+ width: kResolveTargetSize,
+ height: kResolveTargetSize,
+ depthOrArrayLayers: t.params.resolveTargetBaseArrayLayer + 1
+ },
+ sampleCount: 1,
+ mipLevelCount: t.params.resolveTargetBaseMipLevel + 1,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ // Clear to black for the load operation. After the draw, the top left half of the attachment
+ // will be white and the bottom right half will be black.
+ renderPassColorAttachments.push({
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: t.params.storeOperation,
+ resolveTarget: resolveTarget.createView({
+ baseMipLevel: t.params.resolveTargetBaseMipLevel,
+ baseArrayLayer: t.params.resolveTargetBaseArrayLayer
+ })
+ });
+
+ resolveTargets.push(resolveTarget);
+ } else {
+ renderPassColorAttachments.push({
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: t.params.storeOperation
+ });
+ }
+ }
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pass = encoder.beginRenderPass({
+ colorAttachments: renderPassColorAttachments
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ // Verify the resolve targets contain the correct values. Note that we use z to specify the
+ // array layer from which to pull the pixels for testing.
+ const z = t.params.resolveTargetBaseArrayLayer;
+ for (const resolveTarget of resolveTargets) {
+ t.expectSinglePixelComparisonsAreOkInTexture(
+ { texture: resolveTarget, mipLevel: t.params.resolveTargetBaseMipLevel },
+ [
+ // Top left pixel should be {1.0, 1.0, 1.0, 1.0}.
+ { coord: { x: 0, y: 0, z }, exp: { R: 1.0, G: 1.0, B: 1.0, A: 1.0 } },
+ // Bottom right pixel should be {0, 0, 0, 0}.
+ { coord: { x: kSize - 1, y: kSize - 1, z }, exp: { R: 0, G: 0, B: 0, A: 0 } },
+ // Top right pixel should be {0.5, 0.5, 0.5, 0.5} due to the multisampled resolve.
+ { coord: { x: kSize - 1, y: 0, z }, exp: { R: 0.5, G: 0.5, B: 0.5, A: 0.5 } }]
+
+ );
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js
new file mode 100644
index 0000000000..16ce846699
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeOp.spec.js
@@ -0,0 +1,354 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `API Operation Tests for RenderPass StoreOp.
+
+ Test Coverage:
+
+ - Tests that color and depth-stencil store operations {'discard', 'store'} work correctly for a
+ render pass with both a color attachment and depth-stencil attachment.
+ TODO: use depth24plus-stencil8
+
+ - Tests that store operations {'discard', 'store'} work correctly for a render pass with multiple
+ color attachments.
+ TODO: test with more interesting loadOp values
+
+ - Tests that store operations {'discard', 'store'} work correctly for a render pass with a color
+ attachment for:
+ - All renderable color formats
+ - mip level set to {'0', mip > '0'}
+ - array layer set to {'0', layer > '1'} for 2D textures
+ TODO: depth slice set to {'0', slice > '0'} for 3D textures
+
+ - Tests that store operations {'discard', 'store'} work correctly for a render pass with a
+ depth-stencil attachment for:
+ - All renderable depth-stencil formats
+ - mip level set to {'0', mip > '0'}
+ - array layer set to {'0', layer > '1'} for 2D textures
+ TODO: test depth24plus and depth24plus-stencil8 formats
+ TODO: test that depth and stencil aspects are set separately
+ TODO: depth slice set to {'0', slice > '0'} for 3D textures
+ TODO: test with more interesting loadOp values`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ kTextureFormatInfo,
+ kEncodableTextureFormats,
+ kSizedDepthStencilFormats } from
+'../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+
+// Test with a zero and non-zero mip.
+const kMipLevel = [0, 1];
+const kMipLevelCount = 2;
+
+// Test with different numbers of color attachments.
+
+const kNumColorAttachments = [1, 2, 3, 4];
+
+// Test with a zero and non-zero array layer.
+const kArrayLayers = [0, 1];
+
+const kStoreOps = ['discard', 'store'];
+
+const kHeight = 2;
+const kWidth = 2;
+
+export const g = makeTestGroup(GPUTest);
+
+// Tests a render pass with both a color and depth stencil attachment to ensure store operations are
+// set independently.
+g.test('render_pass_store_op,color_attachment_with_depth_stencil_attachment').
+params((u) =>
+u //
+.combine('colorStoreOperation', kStoreOps).
+combine('depthStencilStoreOperation', kStoreOps)
+).
+fn((t) => {
+ // Create a basic color attachment.
+ const kColorFormat = 'rgba8unorm';
+ const colorAttachment = t.device.createTexture({
+ format: kColorFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const colorAttachmentView = colorAttachment.createView();
+
+ // Create a basic depth/stencil attachment.
+ const kDepthStencilFormat = 'depth32float';
+ const depthStencilAttachment = t.device.createTexture({
+ format: kDepthStencilFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ // Color load operation will clear to {1.0, 1.0, 1.0, 1.0}.
+ // Depth operation will clear to 1.0.
+ // Store operations are determined by test the params.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: t.params.colorStoreOperation
+ }],
+
+ depthStencilAttachment: {
+ view: depthStencilAttachment.createView(),
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: t.params.depthStencilStoreOperation
+ }
+ });
+ pass.end();
+
+ t.device.queue.submit([encoder.finish()]);
+
+ // Check that the correct store operation occurred.
+ let expectedColorValue = {};
+ if (t.params.colorStoreOperation === 'discard') {
+ // If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
+ expectedColorValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
+ } else if (t.params.colorStoreOperation === 'store') {
+ // If colorStoreOP was store, the texture should still contain {1.0, 1.0, 1.0, 1.0}.
+ expectedColorValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+ }
+ t.expectSingleColor(colorAttachment, kColorFormat, {
+ size: [kHeight, kWidth, 1],
+ exp: expectedColorValue
+ });
+
+ // Check that the correct store operation occurred.
+ let expectedDepthValue = {};
+ if (t.params.depthStencilStoreOperation === 'discard') {
+ // If depthStencilStoreOperation was clear, the texture's depth component should be 0.0, and
+ // the stencil component should be 0.0.
+ expectedDepthValue = { Depth: 0.0 };
+ } else if (t.params.depthStencilStoreOperation === 'store') {
+ // If depthStencilStoreOperation was store, the texture's depth component should be 1.0, and
+ // the stencil component should be 1.0.
+ expectedDepthValue = { Depth: 1.0 };
+ }
+ t.expectSingleColor(depthStencilAttachment, kDepthStencilFormat, {
+ size: [kHeight, kWidth, 1],
+ exp: expectedDepthValue,
+ layout: { mipLevel: 0, aspect: 'depth-only' }
+ });
+});
+
+// Tests that render pass color attachment store operations work correctly for all renderable color
+// formats, mip levels and array layers.
+g.test('render_pass_store_op,color_attachment_only').
+params((u) =>
+u.
+combine('colorFormat', kEncodableTextureFormats)
+// Filter out any non-renderable formats
+.filter(({ colorFormat }) => !!kTextureFormatInfo[colorFormat].colorRender).
+combine('storeOperation', kStoreOps).
+beginSubcases().
+combine('mipLevel', kMipLevel).
+combine('arrayLayer', kArrayLayers)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.colorFormat);
+}).
+fn((t) => {
+ const colorAttachment = t.device.createTexture({
+ format: t.params.colorFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: t.params.arrayLayer + 1 },
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const colorViewDesc = {
+ baseArrayLayer: t.params.arrayLayer,
+ baseMipLevel: t.params.mipLevel,
+ mipLevelCount: 1,
+ arrayLayerCount: 1
+ };
+
+ const colorAttachmentView = colorAttachment.createView(colorViewDesc);
+
+ // Color load operation will clear to {1.0, 0.0, 0.0, 1.0}.
+ // Color store operation is determined by the test params.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: t.params.storeOperation
+ }]
+
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ // Check that the correct store operation occurred.
+ let expectedValue = {};
+ if (t.params.storeOperation === 'discard') {
+ // If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
+ expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
+ } else if (t.params.storeOperation === 'store') {
+ // If colorStoreOP was store, the texture should still contain {1.0, 0.0, 0.0, 1.0}.
+ expectedValue = { R: 1.0, G: 0.0, B: 0.0, A: 1.0 };
+ }
+
+ t.expectSingleColor(colorAttachment, t.params.colorFormat, {
+ size: [kHeight, kWidth, 1],
+ slice: t.params.arrayLayer,
+ exp: expectedValue,
+ layout: { mipLevel: t.params.mipLevel }
+ });
+});
+
+// Test with multiple color attachments to ensure each attachment's storeOp is set independently.
+g.test('render_pass_store_op,multiple_color_attachments').
+params((u) =>
+u.
+combine('storeOperation1', kStoreOps).
+combine('storeOperation2', kStoreOps).
+beginSubcases().
+combine('colorAttachments', kNumColorAttachments)
+).
+fn((t) => {
+ const kColorFormat = 'rgba8unorm';
+ const colorAttachments = [];
+
+ for (let i = 0; i < t.params.colorAttachments; i++) {
+ colorAttachments.push(
+ t.device.createTexture({
+ format: kColorFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+ }
+
+ // Color load operation will clear to {1.0, 1.0, 1.0, 1.0}
+ // Color store operation is determined by test params. Use storeOperation1 for even numbered
+ // attachments and storeOperation2 for odd numbered attachments.
+ const renderPassColorAttachments = [];
+ for (let i = 0; i < t.params.colorAttachments; i++) {
+ renderPassColorAttachments.push({
+ view: colorAttachments[i].createView(),
+ clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: i % 2 === 0 ? t.params.storeOperation1 : t.params.storeOperation2
+ });
+ }
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: renderPassColorAttachments
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ // Check that the correct store operation occurred.
+ let expectedValue = {};
+ for (let i = 0; i < t.params.colorAttachments; i++) {
+ if (renderPassColorAttachments[i].storeOp === 'discard') {
+ // If colorStoreOp was clear, the texture should now contain {0.0, 0.0, 0.0, 0.0}.
+ expectedValue = { R: 0.0, G: 0.0, B: 0.0, A: 0.0 };
+ } else if (renderPassColorAttachments[i].storeOp === 'store') {
+ // If colorStoreOP was store, the texture should still contain {1.0, 1.0, 1.0, 1.0}.
+ expectedValue = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+ }
+ t.expectSingleColor(colorAttachments[i], kColorFormat, {
+ size: [kHeight, kWidth, 1],
+ exp: expectedValue
+ });
+ }
+});
+
+g.test('render_pass_store_op,depth_stencil_attachment_only').
+desc(
+ `
+Tests that render pass depth stencil store operations work correctly for all renderable color
+formats, mip levels and array layers.
+
+- x= all (sized) depth stencil formats, all store ops, multiple mip levels, multiple array layers
+
+TODO: Also test unsized depth/stencil formats [1]
+ `
+).
+params((u) =>
+u.
+combine('depthStencilFormat', kSizedDepthStencilFormats) // [1]
+.combine('storeOperation', kStoreOps).
+beginSubcases().
+combine('mipLevel', kMipLevel).
+combine('arrayLayer', kArrayLayers)
+).
+fn((t) => {
+ const depthStencilTexture = t.device.createTexture({
+ format: t.params.depthStencilFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: t.params.arrayLayer + 1 },
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const depthStencilViewDesc = {
+ baseArrayLayer: t.params.arrayLayer,
+ baseMipLevel: t.params.mipLevel,
+ mipLevelCount: 1,
+ arrayLayerCount: 1
+ };
+
+ const depthStencilAttachmentView = depthStencilTexture.createView(depthStencilViewDesc);
+
+ // Depth-stencil load operation will clear to depth = 1.0, stencil = 1.0.
+ // Depth-stencil store operate is determined by test params.
+ const encoder = t.device.createCommandEncoder();
+ const depthStencilAttachment = {
+ view: depthStencilAttachmentView
+ };
+ if (kTextureFormatInfo[t.params.depthStencilFormat].depth) {
+ depthStencilAttachment.depthClearValue = 1.0;
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = t.params.storeOperation;
+ }
+ if (kTextureFormatInfo[t.params.depthStencilFormat].stencil) {
+ depthStencilAttachment.stencilClearValue = 1;
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = t.params.storeOperation;
+ }
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ let expectedDepthValue = {};
+ let expectedStencilValue = {};
+ if (t.params.storeOperation === 'discard') {
+ // If depthStencilStoreOperation was clear, the texture's depth/stencil component should be 0,
+ expectedDepthValue = { Depth: 0.0 };
+ expectedStencilValue = { Stencil: 0 };
+ } else if (t.params.storeOperation === 'store') {
+ // If depthStencilStoreOperation was store, the texture's depth/stencil components should be 1,
+ expectedDepthValue = { Depth: 1.0 };
+ expectedStencilValue = { Stencil: 1 };
+ }
+
+ if (kTextureFormatInfo[t.params.depthStencilFormat].depth) {
+ t.expectSingleColor(depthStencilTexture, t.params.depthStencilFormat, {
+ size: [kHeight, kWidth, 1],
+ slice: t.params.arrayLayer,
+ exp: expectedDepthValue,
+ layout: { mipLevel: t.params.mipLevel, aspect: 'depth-only' }
+ });
+ }
+ if (kTextureFormatInfo[t.params.depthStencilFormat].stencil) {
+ t.expectSingleColor(depthStencilTexture, t.params.depthStencilFormat, {
+ size: [kHeight, kWidth, 1],
+ slice: t.params.arrayLayer,
+ exp: expectedStencilValue,
+ layout: { mipLevel: t.params.mipLevel, aspect: 'stencil-only' }
+ });
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeop2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeop2.spec.js
new file mode 100644
index 0000000000..07894fcfc0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pass/storeop2.spec.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+renderPass store op test that drawn quad is either stored or cleared based on storeop
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('storeOp_controls_whether_1x1_drawn_quad_is_stored').
+desc(
+ `
+TODO: is this duplicated with api,operation,render_pass,storeOp?
+TODO: needs review and rename
+`
+).
+paramsSimple([
+{ storeOp: 'store', _expected: 1 }, //
+{ storeOp: 'discard', _expected: 0 }]
+).
+fn((t) => {
+ const renderTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'r8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ // create render pipeline
+ const renderPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>( 1.0, -1.0),
+ vec2<f32>( 1.0, 1.0),
+ vec2<f32>(-1.0, 1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'r8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ // encode pass and submit
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTexture.createView(),
+ storeOp: t.params.storeOp,
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(renderPipeline);
+ pass.draw(3);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ // expect the buffer to be clear
+ t.expectSingleColor(renderTexture, 'r8unorm', {
+ size: [1, 1, 1],
+ exp: { R: t.params._expected }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js
new file mode 100644
index 0000000000..12e919b64d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/culling_tests.spec.js
@@ -0,0 +1,359 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Test culling and rasterization state.
+
+Test coverage:
+Test all culling combinations of GPUFrontFace and GPUCullMode show the correct output.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+
+function faceIsCulled(face, frontFace, cullMode) {
+ return cullMode !== 'none' && frontFace === face === (cullMode === 'front');
+}
+
+function faceColor(face, frontFace, cullMode) {
+ // front facing color is green, non front facing is red, background is blue
+ const isCulled = faceIsCulled(face, frontFace, cullMode);
+ if (!isCulled && face === frontFace) {
+ return new Uint8Array([0x00, 0xff, 0x00, 0xff]);
+ } else if (isCulled) {
+ return new Uint8Array([0x00, 0x00, 0xff, 0xff]);
+ } else {
+ return new Uint8Array([0xff, 0x00, 0x00, 0xff]);
+ }
+}
+
+class CullingTest extends TextureTestMixin(GPUTest) {
+ checkCornerPixels(
+ texture,
+ expectedTopLeftColor,
+ expectedBottomRightColor)
+ {
+ this.expectSinglePixelComparisonsAreOkInTexture({ texture }, [
+ { coord: { x: 0, y: 0 }, exp: expectedTopLeftColor },
+ { coord: { x: texture.width - 1, y: texture.height - 1 }, exp: expectedBottomRightColor }]
+ );
+ }
+
+ drawFullClipSpaceTriangleAndCheckCornerPixels(
+ texture,
+ format,
+ topology,
+ color,
+ depthStencil,
+ depthStencilAttachment,
+ expectedTopLeftColor,
+ expectedBottomRightColor)
+ {
+ const { device } = this;
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment
+ });
+
+ pass.setPipeline(
+ device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>( 3.0, -1.0),
+ vec2<f32>(-1.0, 3.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4f(${Array.from(color).map((v) => v / 255)});
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ primitive: {
+ topology
+ },
+ depthStencil
+ })
+ );
+ pass.draw(3);
+ pass.end();
+
+ device.queue.submit([encoder.finish()]);
+
+ this.checkCornerPixels(texture, expectedTopLeftColor, expectedBottomRightColor);
+ }
+}
+
+export const g = makeTestGroup(CullingTest);
+
+g.test('culling').
+desc(
+ `
+ Test 2 triangles with different winding orders:
+
+ - Test that the counter-clock wise triangle has correct output for:
+ - All FrontFaces (ccw, cw)
+ - All CullModes (none, front, back)
+ - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)
+ - Some primitive topologies (triangle-list, triangle-strip)
+
+ - Test that the clock wise triangle has correct output for:
+ - All FrontFaces (ccw, cw)
+ - All CullModes (none, front, back)
+ - All depth stencil attachment types (none, depth24plus, depth32float, depth24plus-stencil8)
+ - Some primitive topologies (triangle-list, triangle-strip)
+ `
+).
+params((u) =>
+u.
+combine('frontFace', ['ccw', 'cw']).
+combine('cullMode', ['none', 'front', 'back']).
+beginSubcases().
+combine('depthStencilFormat', [
+null,
+'depth24plus',
+'depth32float',
+'depth24plus-stencil8']
+).
+combine('topology', ['triangle-list', 'triangle-strip'])
+).
+fn((t) => {
+ const { frontFace, cullMode, depthStencilFormat, topology } = t.params;
+ const size = 4;
+ const format = 'rgba8unorm';
+
+ const texture = t.device.createTexture({
+ size: { width: size, height: size, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+
+ const haveStencil = depthStencilFormat && kTextureFormatInfo[depthStencilFormat].stencil;
+ let depthTexture = undefined;
+ let depthStencilAttachment = undefined;
+ let depthStencil = undefined;
+ if (depthStencilFormat) {
+ depthTexture = t.device.createTexture({
+ size: { width: size, height: size, depthOrArrayLayers: 1 },
+ format: depthStencilFormat,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ depthStencilAttachment = {
+ view: depthTexture.createView(),
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store'
+ };
+
+ depthStencil = {
+ format: depthStencilFormat,
+ depthCompare: 'less',
+ depthWriteEnabled: true
+ };
+
+ if (haveStencil) {
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ depthStencil.stencilFront = { passOp: 'increment-clamp' };
+ depthStencil.stencilBack = { passOp: 'increment-clamp' };
+ }
+ }
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ clearValue: [0, 0, 1, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment
+ });
+
+ // Draw triangles with different winding orders:
+ //
+ // for triangle-list, 2 triangles
+ // 1. The top-left one is counterclockwise (CCW)
+ // 2. The bottom-right one is clockwise (CW)
+ //
+ // 0---2---+
+ // | | |
+ // | | |
+ // 1---+---4
+ // | | |
+ // | | |
+ // +---3---5
+ //
+ // for triangle-strip, 4 triangles
+ // note: for triangle-strip the index order swaps every other triangle
+ // so the order is 012, 213, 234, 435
+ //
+ // 1. The top left is counterclockwise (CCW)
+ // 2. zero size
+ // 3. zero size
+ // 4. The bottom right one is clockwise (CW)
+ //
+ // 0
+ // |
+ // |
+ // +---+---+
+ // | | |
+ // | | |
+ // 1---+---23--+---5
+ // | | |
+ // | | |
+ // +---+---+
+ // |
+ // |
+ // 4
+ pass.setPipeline(
+ t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 6> = array<vec2<f32>, 6>(
+ ${
+ topology === 'triangle-list' ?
+ `
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>(-1.0, 0.0),
+ vec2<f32>( 0.0, 1.0),
+ vec2<f32>( 0.0, -1.0),
+ vec2<f32>( 1.0, 0.0),
+ vec2<f32>( 1.0, -1.0));
+ ` :
+ `
+ vec2<f32>( 0.0, 2.0),
+ vec2<f32>(-2.0, 0.0),
+ vec2<f32>( 0.0, 0.0),
+ vec2<f32>( 0.0, 0.0),
+ vec2<f32>( 0.0, -2.0),
+ vec2<f32>( 2.0, 0.0));
+ `
+ }
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main(
+ @builtin(front_facing) FrontFacing : bool
+ ) -> @location(0) vec4<f32> {
+ var color : vec4<f32>;
+ if (FrontFacing) {
+ color = vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ } else {
+ color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }
+ return color;
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ primitive: {
+ topology,
+ frontFace,
+ cullMode
+ },
+ depthStencil
+ })
+ );
+ pass.draw(6);
+ pass.end();
+
+ t.device.queue.submit([encoder.finish()]);
+
+ // front facing color is green, non front facing is red, background is blue
+ const kCCWTriangleTopLeftColor = faceColor('ccw', frontFace, cullMode);
+ const kCWTriangleBottomRightColor = faceColor('cw', frontFace, cullMode);
+ t.checkCornerPixels(texture, kCCWTriangleTopLeftColor, kCWTriangleBottomRightColor);
+
+ if (depthTexture) {
+ // draw a triangle that covers all of clip space in yellow at the same depth
+ // as the previous triangles with the depth test set to 'less'. We should only
+ // draw yellow where the previous triangles did not.
+ depthStencilAttachment.depthLoadOp = 'load';
+
+ if (haveStencil) {
+ depthStencilAttachment.stencilLoadOp = 'load';
+ depthStencil.stencilFront.passOp = 'keep';
+ depthStencil.stencilBack.passOp = 'keep';
+ }
+
+ const k2ndDrawColor = new Uint8Array([255, 255, 0, 255]);
+
+ const isTopLeftCulled = faceIsCulled('ccw', frontFace, cullMode);
+ const kExpectedTopLeftColor = isTopLeftCulled ? k2ndDrawColor : kCCWTriangleTopLeftColor;
+
+ const isBottomRightCulled = faceIsCulled('cw', frontFace, cullMode);
+ const kExpectedBottomRightColor = isBottomRightCulled ?
+ k2ndDrawColor :
+ kCWTriangleBottomRightColor;
+
+ t.drawFullClipSpaceTriangleAndCheckCornerPixels(
+ texture,
+ format,
+ topology,
+ k2ndDrawColor,
+ depthStencil,
+ depthStencilAttachment,
+ kExpectedTopLeftColor,
+ kExpectedBottomRightColor
+ );
+
+ if (haveStencil) {
+ // draw a triangle that covers all of clip space in cyan with the stencil
+ // compare set to 'equal'. The reference value defaults to 0 so we should
+ // only render cyan where the first two triangles did not.
+ depthStencil.depthCompare = 'always';
+ depthStencil.stencilFront.compare = 'equal';
+ depthStencil.stencilBack.compare = 'equal';
+
+ const k3rdDrawColor = new Uint8Array([0, 255, 255, 255]);
+ const kExpectedTopLeftColor = isTopLeftCulled ? k3rdDrawColor : kCCWTriangleTopLeftColor;
+ const kExpectedBottomRightColor = isBottomRightCulled ?
+ k3rdDrawColor :
+ kCWTriangleBottomRightColor;
+
+ t.drawFullClipSpaceTriangleAndCheckCornerPixels(
+ texture,
+ format,
+ topology,
+ k3rdDrawColor,
+ depthStencil,
+ depthStencilAttachment,
+ kExpectedTopLeftColor,
+ kExpectedBottomRightColor
+ );
+ }
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/overrides.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/overrides.spec.js
new file mode 100644
index 0000000000..be7a059954
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/overrides.spec.js
@@ -0,0 +1,453 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Testing render pipeline using overridable constants in vertex stage and fragment stage.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+
+class F extends GPUTest {
+ async ExpectShaderOutputWithConstants(
+ isAsync,
+ format,
+ expected,
+ vertex,
+ fragment)
+ {
+ const renderTarget = this.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const descriptor = {
+ layout: 'auto',
+ vertex,
+ fragment,
+ primitive: {
+ topology: 'triangle-list',
+ frontFace: 'ccw',
+ cullMode: 'back'
+ }
+ };
+
+ const promise = isAsync ?
+ this.device.createRenderPipelineAsync(descriptor) :
+ Promise.resolve(this.device.createRenderPipeline(descriptor));
+
+ const pipeline = await promise;
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ clearValue: {
+ r: kClearValueResult.R,
+ g: kClearValueResult.G,
+ b: kClearValueResult.B,
+ a: kClearValueResult.A
+ },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ this.expectSingleColor(renderTarget, format, {
+ size: [1, 1, 1],
+ exp: expected
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kClearValueResult = { R: 0.2, G: 0.4, B: 0.6, A: 0.8 };
+const kDefaultValueResult = { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+
+const kFullScreenTriangleVertexShader = `
+override xright: f32 = 3.0;
+override ytop: f32 = 3.0;
+
+@vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, ytop),
+ vec2<f32>(-1.0, -ytop),
+ vec2<f32>(xright, 0.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+}
+`;
+
+const kFullScreenTriangleFragmentShader = `
+override R: f32 = 1.0;
+override G: f32 = 1.0;
+override B: f32 = 1.0;
+override A: f32 = 1.0;
+
+@fragment fn main()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(R, G, B, A);
+}
+`;
+
+g.test('basic').
+desc(
+ `Test that either correct constants override values or default values when no constants override value are provided at pipeline creation time are used correctly in vertex and fragment shader.`
+).
+params((u) =>
+u.
+combine('isAsync', [true, false]).
+beginSubcases().
+combineWithParams([
+{
+ expected: kDefaultValueResult,
+ vertexConstants: {},
+ fragmentConstants: {}
+},
+{
+ expected: kClearValueResult,
+ vertexConstants: {
+ xright: -3.0
+ },
+ fragmentConstants: {}
+},
+{
+ expected: kClearValueResult,
+ vertexConstants: {
+ ytop: -3.0
+ },
+ fragmentConstants: {}
+},
+{
+ expected: kDefaultValueResult,
+ vertexConstants: {
+ xright: 4.0,
+ ytop: 4.0
+ },
+ fragmentConstants: {}
+},
+{
+ expected: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 0.0, B: 0.0 }
+},
+{
+ expected: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 }
+
+
+
+}]
+)
+).
+fn(async (t) => {
+ const format = 'bgra8unorm';
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ format,
+ t.params.expected,
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleVertexShader
+ }),
+ entryPoint: 'main',
+ constants: t.params.vertexConstants
+ },
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleFragmentShader
+ }),
+ entryPoint: 'main',
+ constants: t.params.fragmentConstants,
+ targets: [{ format }]
+ }
+ );
+});
+
+g.test('precision').
+desc(`Test that the float number precision is preserved for constants`).
+params((u) =>
+u.
+combine('isAsync', [true, false]).
+beginSubcases().
+combineWithParams([
+{
+ expected: { R: 3.14159, G: 1.0, B: 1.0, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 3.14159 }
+},
+{
+ expected: { R: 3.141592653589793, G: 1.0, B: 1.0, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { R: 3.141592653589793 }
+}]
+)
+).
+fn(async (t) => {
+ const format = 'rgba32float';
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ format,
+ t.params.expected,
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleVertexShader
+ }),
+ entryPoint: 'main',
+ constants: t.params.vertexConstants
+ },
+ {
+ module: t.device.createShaderModule({
+ code: kFullScreenTriangleFragmentShader
+ }),
+ entryPoint: 'main',
+ constants: t.params.fragmentConstants,
+ targets: [{ format }]
+ }
+ );
+});
+
+g.test('shared_shader_module').
+desc(
+ `Test that when the same module is shared by different pipelines, the constant values are still being used correctly.`
+).
+params((u) =>
+u.
+combine('isAsync', [true, false]).
+beginSubcases().
+combineWithParams([
+{
+ expected0: kClearValueResult,
+ vertexConstants0: {
+ xright: -3.0
+ },
+ fragmentConstants0: {},
+
+ expected1: kDefaultValueResult,
+ vertexConstants1: {},
+ fragmentConstants1: {}
+},
+{
+ expected0: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 },
+ vertexConstants0: {},
+ fragmentConstants0: { R: 0.0, G: 0.0, B: 0.0, A: 0.0 },
+
+
+
+
+ expected1: kDefaultValueResult,
+ vertexConstants1: {},
+ fragmentConstants1: {}
+},
+{
+ expected0: { R: 1.0, G: 0.0, B: 1.0, A: 0.0 },
+ vertexConstants0: {},
+ fragmentConstants0: { R: 1.0, G: 0.0, B: 1.0, A: 0.0 },
+
+
+
+
+ expected1: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
+ vertexConstants1: {},
+ fragmentConstants1: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 }
+
+
+
+}]
+)
+).
+fn(async (t) => {
+ const format = 'bgra8unorm';
+ const vertexModule = t.device.createShaderModule({
+ code: kFullScreenTriangleVertexShader
+ });
+
+ const fragmentModule = t.device.createShaderModule({
+ code: kFullScreenTriangleFragmentShader
+ });
+
+ const createPipelineFn = async (
+ vertexConstants,
+ fragmentConstants) =>
+ {
+ const descriptor = {
+ layout: 'auto',
+ vertex: {
+ module: vertexModule,
+ entryPoint: 'main',
+ constants: vertexConstants
+ },
+ fragment: {
+ module: fragmentModule,
+ entryPoint: 'main',
+ targets: [{ format }],
+ constants: fragmentConstants
+ },
+ primitive: {
+ topology: 'triangle-list',
+ frontFace: 'ccw',
+ cullMode: 'back'
+ }
+ };
+
+ return t.params.isAsync ?
+ t.device.createRenderPipelineAsync(descriptor) :
+ t.device.createRenderPipeline(descriptor);
+ };
+
+ const pipeline0 = await createPipelineFn(
+ t.params.vertexConstants0,
+ t.params.fragmentConstants0
+ );
+ const pipeline1 = await createPipelineFn(
+ t.params.vertexConstants1,
+ t.params.fragmentConstants1
+ );
+
+ const renderTarget0 = t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const renderTarget1 = t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pass0 = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget0.createView(),
+ storeOp: 'store',
+ clearValue: {
+ r: kClearValueResult.R,
+ g: kClearValueResult.G,
+ b: kClearValueResult.B,
+ a: kClearValueResult.A
+ },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass0.setPipeline(pipeline0);
+ pass0.draw(3);
+ pass0.end();
+
+ const pass1 = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget1.createView(),
+ storeOp: 'store',
+ clearValue: {
+ r: kClearValueResult.R,
+ g: kClearValueResult.G,
+ b: kClearValueResult.B,
+ a: kClearValueResult.A
+ },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass1.setPipeline(pipeline1);
+ pass1.draw(3);
+ pass1.end();
+
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectSingleColor(renderTarget0, format, {
+ size: [1, 1, 1],
+ exp: t.params.expected0
+ });
+ t.expectSingleColor(renderTarget1, format, {
+ size: [1, 1, 1],
+ exp: t.params.expected1
+ });
+});
+
+g.test('multi_entry_points').
+desc(
+ `Test that when the same module is shared by vertex and fragment shader, the constant values are still being used correctly.`
+).
+params((u) =>
+u.
+combine('isAsync', [true, false]).
+beginSubcases().
+combineWithParams([
+{
+ expected: { R: 0.8, G: 0.4, B: 0.2, A: 1.0 },
+ vertexConstants: { A: 4.0, B: 4.0 },
+ fragmentConstants: { A: 0.8, B: 0.4, C: 0.2, D: 1.0 }
+
+
+
+},
+{
+ expected: { R: 0.8, G: 0.4, B: 0.2, A: 1.0 },
+ vertexConstants: {},
+ fragmentConstants: { A: 0.8, B: 0.4, C: 0.2, D: 1.0 }
+
+
+
+},
+{
+ expected: kClearValueResult,
+ vertexConstants: { A: -3.0 },
+ fragmentConstants: { A: 0.8, B: 0.4, C: 0.2, D: 1.0 }
+
+
+
+}]
+)
+).
+fn(async (t) => {
+ const format = 'bgra8unorm';
+ const module = t.device.createShaderModule({
+ code: `
+ override A: f32 = 3.0;
+ override B: f32 = 3.0;
+ override C: f32;
+ override D: f32;
+
+ @vertex fn vertexMain(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, A),
+ vec2<f32>(-1.0, -A),
+ vec2<f32>(B, 0.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+
+ @fragment fn fragmentMain()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(A, B, C, D);
+ }
+ `
+ });
+ await t.ExpectShaderOutputWithConstants(
+ t.params.isAsync,
+ format,
+ t.params.expected,
+ {
+ module,
+ entryPoint: 'vertexMain',
+ constants: t.params.vertexConstants
+ },
+ {
+ module,
+ entryPoint: 'fragmentMain',
+ constants: t.params.fragmentConstants,
+ targets: [{ format }]
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.js
new file mode 100644
index 0000000000..5fdd9a2b66
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.js
@@ -0,0 +1,450 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+- Test pipeline outputs with different color attachment number, formats, component counts, etc.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import {
+ computeBytesPerSampleFromFormats,
+ kRenderableColorTextureFormats,
+ kTextureFormatInfo } from
+'../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { getFragmentShaderCodeWithOutput, getPlainTypeInfo } from '../../../util/shader.js';
+import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
+
+const kVertexShader = `
+@vertex fn main(
+@builtin(vertex_index) VertexIndex : u32
+) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -3.0),
+ vec2<f32>(3.0, 1.0),
+ vec2<f32>(-1.0, 1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+}
+`;
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+// Values to write into each attachment
+// We make values different for each attachment index and each channel
+// to make sure they didn't get mixed up
+
+// Clamp alpha to 3 to avoid comparing a large expected value with a max 3 value for rgb10a2uint
+// MAINTENANCE_TODO: Make TexelRepresentation.numericRange per-component and use that.
+const attachmentsIntWriteValues = [
+{ R: 1, G: 2, B: 3, A: 1 },
+{ R: 5, G: 6, B: 7, A: 2 },
+{ R: 9, G: 10, B: 11, A: 3 },
+{ R: 13, G: 14, B: 15, A: 0 }];
+
+const attachmentsFloatWriteValues = [
+{ R: 0.12, G: 0.34, B: 0.56, A: 0 },
+{ R: 0.78, G: 0.9, B: 0.19, A: 1 },
+{ R: 0.28, G: 0.37, B: 0.46, A: 0.3 },
+{ R: 0.55, G: 0.64, B: 0.73, A: 1 }];
+
+
+g.test('color,attachments').
+desc(`Test that pipeline with sparse color attachments write values correctly.`).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+beginSubcases().
+combine('attachmentCount', [2, 3, 4]).
+expand('emptyAttachmentId', (p) => range(p.attachmentCount, (i) => i))
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, attachmentCount, emptyAttachmentId } = t.params;
+ const componentCount = kTexelRepresentationInfo[format].componentOrder.length;
+ const info = kTextureFormatInfo[format];
+
+ // We only need to test formats that have a valid color attachment bytes per sample.
+ const pixelByteCost = kTextureFormatInfo[format].colorRender?.byteCost;
+ t.skipIf(
+ pixelByteCost === undefined ||
+ computeBytesPerSampleFromFormats(range(attachmentCount, () => format)) >
+ t.device.limits.maxColorAttachmentBytesPerSample
+ );
+
+ const writeValues =
+ info.color.type === 'sint' || info.color.type === 'uint' ?
+ attachmentsIntWriteValues :
+ attachmentsFloatWriteValues;
+
+ const renderTargets = range(attachmentCount, () =>
+ t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kVertexShader
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: getFragmentShaderCodeWithOutput(
+ range(attachmentCount, (i) =>
+ i === emptyAttachmentId ?
+ null :
+ {
+ values: [
+ writeValues[i].R,
+ writeValues[i].G,
+ writeValues[i].B,
+ writeValues[i].A],
+
+ plainType: getPlainTypeInfo(info.color.type),
+ componentCount
+ }
+ )
+ )
+ }),
+ entryPoint: 'main',
+ targets: range(attachmentCount, (i) => i === emptyAttachmentId ? null : { format })
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: range(attachmentCount, (i) =>
+ i === emptyAttachmentId ?
+ null :
+ {
+ view: renderTargets[i].createView(),
+ storeOp: 'store',
+ clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 0.5 },
+ loadOp: 'clear'
+ }
+ )
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ for (let i = 0; i < attachmentCount; i++) {
+ if (i === emptyAttachmentId) {
+ continue;
+ }
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: renderTargets[i] }, [
+ { coord: { x: 0, y: 0 }, exp: writeValues[i] }]
+ );
+ }
+});
+
+g.test('color,component_count').
+desc(
+ `Test that extra components of the output (e.g. f32, vec2<f32>, vec3<f32>, vec4<f32>) are discarded.`
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+beginSubcases().
+combine('componentCount', [1, 2, 3, 4]).
+filter((x) => x.componentCount >= kTexelRepresentationInfo[x.format].componentOrder.length)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, componentCount } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ // expected RGBA values
+ // extra channels are discarded
+ const values = [0, 1, 0, 1];
+
+ const renderTarget = t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kVertexShader
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: getFragmentShaderCodeWithOutput([
+ {
+ values,
+ plainType: getPlainTypeInfo(info.color.type),
+ componentCount
+ }]
+ )
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectSingleColor(renderTarget, format, {
+ size: [1, 1, 1],
+ exp: { R: values[0], G: values[1], B: values[2], A: values[3] }
+ });
+});
+
+g.test('color,component_count,blend').
+desc(
+ `Test that blending behaves correctly when:
+- fragment output has no alpha, but the src alpha is not used for the blend operation indicated by blend factors
+- attachment format has no alpha, and the dst alpha should be assumed as 1
+
+The attachment has a load value of [1, 0, 0, 1]
+`
+).
+params((u) =>
+u.
+combine('format', ['r8unorm', 'rg8unorm', 'rgba8unorm', 'bgra8unorm']).
+beginSubcases()
+// _result is expected values in the color attachment (extra channels are discarded)
+// output is the fragment shader output vector
+// 0.498 -> 0x7f, 0.502 -> 0x80
+.combineWithParams([
+// fragment output has no alpha
+{
+ _result: [0, 0, 0, 0],
+ output: [0],
+ colorSrcFactor: 'one',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [0, 0, 0, 0],
+ output: [0],
+ colorSrcFactor: 'dst-alpha',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [1, 0, 0, 0],
+ output: [0],
+ colorSrcFactor: 'one-minus-dst-alpha',
+ colorDstFactor: 'dst-alpha',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'one'
+},
+{
+ _result: [0.498, 0, 0, 0],
+ output: [0.498],
+ colorSrcFactor: 'dst-alpha',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'one'
+},
+{
+ _result: [0, 1, 0, 0],
+ output: [0, 1],
+ colorSrcFactor: 'one',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [0, 1, 0, 0],
+ output: [0, 1],
+ colorSrcFactor: 'dst-alpha',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [1, 0, 0, 0],
+ output: [0, 1],
+ colorSrcFactor: 'one-minus-dst-alpha',
+ colorDstFactor: 'dst-alpha',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'one'
+},
+{
+ _result: [0, 1, 0, 0],
+ output: [0, 1, 0],
+ colorSrcFactor: 'one',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [0, 1, 0, 0],
+ output: [0, 1, 0],
+ colorSrcFactor: 'dst-alpha',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [1, 0, 0, 0],
+ output: [0, 1, 0],
+ colorSrcFactor: 'one-minus-dst-alpha',
+ colorDstFactor: 'dst-alpha',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'one'
+},
+// fragment output has alpha
+{
+ _result: [0.502, 1, 0, 0.498],
+ output: [0, 1, 0, 0.498],
+ colorSrcFactor: 'one',
+ colorDstFactor: 'one-minus-src-alpha',
+ alphaSrcFactor: 'one',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [0.502, 0.498, 0, 0.498],
+ output: [0, 1, 0, 0.498],
+ colorSrcFactor: 'src-alpha',
+ colorDstFactor: 'one-minus-src-alpha',
+ alphaSrcFactor: 'one',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [0, 1, 0, 0.498],
+ output: [0, 1, 0, 0.498],
+ colorSrcFactor: 'dst-alpha',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'one',
+ alphaDstFactor: 'zero'
+},
+{
+ _result: [0, 1, 0, 0.498],
+ output: [0, 1, 0, 0.498],
+ colorSrcFactor: 'dst-alpha',
+ colorDstFactor: 'zero',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'src'
+},
+{
+ _result: [1, 0, 0, 1],
+ output: [0, 1, 0, 0.498],
+ colorSrcFactor: 'one-minus-dst-alpha',
+ colorDstFactor: 'dst-alpha',
+ alphaSrcFactor: 'zero',
+ alphaDstFactor: 'dst-alpha'
+}]
+).
+filter((x) => x.output.length >= kTexelRepresentationInfo[x.format].componentOrder.length)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ format,
+ _result,
+ output,
+ colorSrcFactor,
+ colorDstFactor,
+ alphaSrcFactor,
+ alphaDstFactor
+ } = t.params;
+ const componentCount = output.length;
+ const info = kTextureFormatInfo[format];
+
+ const renderTarget = t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kVertexShader
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: getFragmentShaderCodeWithOutput([
+ {
+ values: output,
+ plainType: getPlainTypeInfo(info.color.type),
+ componentCount
+ }]
+ )
+ }),
+ entryPoint: 'main',
+ targets: [
+ {
+ format,
+ blend: {
+ color: {
+ srcFactor: colorSrcFactor,
+ dstFactor: colorDstFactor,
+ operation: 'add'
+ },
+ alpha: {
+ srcFactor: alphaSrcFactor,
+ dstFactor: alphaDstFactor,
+ operation: 'add'
+ }
+ }
+ }]
+
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectSingleColor(renderTarget, format, {
+ size: [1, 1, 1],
+ exp: { R: _result[0], G: _result[1], B: _result[2], A: _result[3] }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/primitive_topology.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/primitive_topology.spec.js
new file mode 100644
index 0000000000..bcdcf5bf00
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/primitive_topology.spec.js
@@ -0,0 +1,488 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Test primitive topology rendering.
+
+Draw a primitive using 6 vertices with each topology and check if the pixel is covered.
+
+Vertex sequence and coordinates are the same for each topology:
+ - Vertex buffer = [v1, v2, v3, v4, v5, v6]
+ - Topology = [point-list, line-list, line-strip, triangle-list, triangle-strip]
+
+Test locations are framebuffer coordinates:
+ - Pixel value { valid: green, invalid: black, format: 'rgba8unorm'}
+ - Test point is valid if the pixel value equals the covered pixel value at the test location.
+ - Primitive restart occurs for strips (line-strip and triangle-strip) between [v3, v4].
+
+ Topology: point-list Valid test location(s) Invalid test location(s)
+
+ v2 v4 v6 Every vertex. Line-strip locations.
+ Triangle-list locations.
+ Triangle-strip locations.
+
+ v1 v3 v5
+
+ Topology: line-list (3 lines)
+
+ v2 v4 v6 Center of three line segments: Line-strip locations.
+ * * * {v1,V2}, {v3,v4}, and {v4,v5}. Triangle-list locations.
+ * * * Triangle-strip locations.
+ * * *
+ v1 v3 v5
+
+ Topology: line-strip (5 lines)
+
+ v2 v4 v6
+ ** ** *
+ * * * * * Line-list locations Triangle-list locations.
+ * ** ** + Center of two line segments: Triangle-strip locations.
+ v1 v3 v5 {v2,v3} and {v4,v5}.
+ With primitive restart:
+ Line segment {v3, v4}.
+
+ Topology: triangle-list (2 triangles)
+
+ v2 v4 v6
+ ** ****** Center of two triangle(s): Triangle-strip locations.
+ **** **** {v1,v2,v3} and {v4,v5,v6}.
+ ****** **
+ v1 v3 v5
+
+ Topology: triangle-strip (4 triangles)
+
+ v2 v4 v6
+ ** ****** ** ****** Triangle-list locations None.
+ **** **** **** **** + Center of two triangle(s):
+ ****** ** ****** ** {v2,v3,v4} and {v3,v4,v5}. With primitive restart:
+ v1 v3 v5 Triangle {v2, v3, v4}
+ and {v3, v4, v5}.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+
+
+const kRTSize = 56;
+const kColorFormat = 'rgba8unorm';
+const kValidPixelColor = new Uint8Array([0x00, 0xff, 0x00, 0xff]); // green
+const kInvalidPixelColor = new Uint8Array([0x00, 0x00, 0x00, 0x00]); // black
+
+class Point2D {
+
+
+
+
+
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ this.z = 0;
+ this.w = 1;
+ }
+
+ toNDC() {
+ // NDC coordinate space is y-up, so we negate the y mapping.
+ // To ensure the resulting vertex in NDC will be placed at the center of the pixel, we
+ // must offset by the pixel coordinates or 0.5.
+ return new Point2D(2 * (this.x + 0.5) / kRTSize - 1, -2 * (this.y + 0.5) / kRTSize + 1);
+ }
+
+ static getMidpoint(a, b) {
+ return new Point2D((a.x + b.x) / 2, (a.y + b.y) / 2);
+ }
+
+ static getCentroid(a, b, c) {
+ return new Point2D((a.x + b.x + c.x) / 3, (a.y + b.y + c.y) / 3);
+ }
+}
+
+
+
+const VertexLocations = [
+new Point2D(8, 24), // v1
+new Point2D(16, 8), // v2
+new Point2D(24, 24), // v3
+new Point2D(32, 8), // v4
+new Point2D(40, 24), // v5
+new Point2D(48, 8) // v6
+];
+
+function getPointTestLocations(expectedColor) {
+ // Test points are always equal to vertex locations.
+ const testLocations = [];
+ for (const location of VertexLocations) {
+ testLocations.push({ coord: location, exp: expectedColor });
+ }
+ return testLocations;
+}
+
+function getLineTestLocations(expectedColor) {
+ // Midpoints of 3 line segments
+ return [
+ {
+ // Line {v1, v2}
+ coord: Point2D.getMidpoint(VertexLocations[0], VertexLocations[1]),
+ exp: expectedColor
+ },
+ {
+ // Line {v3, v4}
+ coord: Point2D.getMidpoint(VertexLocations[2], VertexLocations[3]),
+ exp: expectedColor
+ },
+ {
+ // Line {v5, v6}
+ coord: Point2D.getMidpoint(VertexLocations[4], VertexLocations[5]),
+ exp: expectedColor
+ }];
+
+}
+
+function getPrimitiveRestartLineTestLocations(expectedColor) {
+ // Midpoints of 2 line segments
+ return [
+ {
+ // Line {v1, v2}
+ coord: Point2D.getMidpoint(VertexLocations[0], VertexLocations[1]),
+ exp: expectedColor
+ },
+ {
+ // Line {v5, v6}
+ coord: Point2D.getMidpoint(VertexLocations[4], VertexLocations[5]),
+ exp: expectedColor
+ }];
+
+}
+
+function getLineStripTestLocations(expectedColor) {
+ // Midpoints of 2 line segments
+ return [
+ {
+ // Line {v2, v3}
+ coord: Point2D.getMidpoint(VertexLocations[1], VertexLocations[2]),
+ exp: expectedColor
+ },
+ {
+ // Line {v4, v5}
+ coord: Point2D.getMidpoint(VertexLocations[3], VertexLocations[4]),
+ exp: expectedColor
+ }];
+
+}
+
+function getTriangleListTestLocations(expectedColor) {
+ // Center of two triangles
+ return [
+ {
+ // Triangle {v1, v2, v3}
+ coord: Point2D.getCentroid(VertexLocations[0], VertexLocations[1], VertexLocations[2]),
+ exp: expectedColor
+ },
+ {
+ // Triangle {v4, v5, v6}
+ coord: Point2D.getCentroid(VertexLocations[3], VertexLocations[4], VertexLocations[5]),
+ exp: expectedColor
+ }];
+
+}
+
+function getTriangleStripTestLocations(expectedColor) {
+ // Center of two triangles
+ return [
+ {
+ // Triangle {v2, v3, v4}
+ coord: Point2D.getCentroid(VertexLocations[1], VertexLocations[2], VertexLocations[3]),
+ exp: expectedColor
+ },
+ {
+ // Triangle {v3, v4, v5}
+ coord: Point2D.getCentroid(VertexLocations[2], VertexLocations[3], VertexLocations[4]),
+ exp: expectedColor
+ }];
+
+}
+
+function getDefaultTestLocations({
+ topology,
+ primitiveRestart = false,
+ invalidateLastInList = false
+
+
+
+
+}) {
+ function maybeInvalidateLast(locations) {
+ if (!invalidateLastInList) return locations;
+
+ return locations.map((tl, i) => {
+ if (i === locations.length - 1) {
+ return {
+ coord: tl.coord,
+ exp: kInvalidPixelColor
+ };
+ } else {
+ return tl;
+ }
+ });
+ }
+
+ let testLocations;
+ switch (topology) {
+ case 'point-list':
+ testLocations = [
+ ...getPointTestLocations(kValidPixelColor),
+ ...getLineStripTestLocations(kInvalidPixelColor),
+ ...getTriangleListTestLocations(kInvalidPixelColor),
+ ...getTriangleStripTestLocations(kInvalidPixelColor)];
+
+ break;
+ case 'line-list':
+ testLocations = [
+ ...maybeInvalidateLast(getLineTestLocations(kValidPixelColor)),
+ ...getLineStripTestLocations(kInvalidPixelColor),
+ ...getTriangleListTestLocations(kInvalidPixelColor),
+ ...getTriangleStripTestLocations(kInvalidPixelColor)];
+
+ break;
+ case 'line-strip':
+ testLocations = [
+ ...(primitiveRestart ?
+ getPrimitiveRestartLineTestLocations(kValidPixelColor) :
+ getLineTestLocations(kValidPixelColor)),
+ ...getLineStripTestLocations(kValidPixelColor),
+ ...getTriangleListTestLocations(kInvalidPixelColor),
+ ...getTriangleStripTestLocations(kInvalidPixelColor)];
+
+ break;
+ case 'triangle-list':
+ testLocations = [
+ ...maybeInvalidateLast(getTriangleListTestLocations(kValidPixelColor)),
+ ...getTriangleStripTestLocations(kInvalidPixelColor)];
+
+ break;
+ case 'triangle-strip':
+ testLocations = [
+ ...getTriangleListTestLocations(kValidPixelColor),
+ ...getTriangleStripTestLocations(primitiveRestart ? kInvalidPixelColor : kValidPixelColor)];
+
+ break;
+ }
+ return testLocations;
+}
+
+function generateVertexBuffer(vertexLocations) {
+ const vertexCoords = new Float32Array(vertexLocations.length * 4);
+ for (let i = 0; i < vertexLocations.length; i++) {
+ const point = vertexLocations[i].toNDC();
+ vertexCoords[i * 4 + 0] = point.x;
+ vertexCoords[i * 4 + 1] = point.y;
+ vertexCoords[i * 4 + 2] = point.z;
+ vertexCoords[i * 4 + 3] = point.w;
+ }
+ return vertexCoords;
+}
+
+const kDefaultDrawCount = 6;
+class PrimitiveTopologyTest extends TextureTestMixin(GPUTest) {
+ makeAttachmentTexture() {
+ return this.device.createTexture({
+ format: kColorFormat,
+ size: { width: kRTSize, height: kRTSize, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ }
+
+ run({
+ topology,
+ indirect,
+ testLocations,
+ primitiveRestart = false,
+ drawCount = kDefaultDrawCount
+
+
+
+
+
+
+ }) {
+ const colorAttachment = this.makeAttachmentTexture();
+
+ // Color load operator will clear color attachment to zero.
+ const encoder = this.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ let stripIndexFormat = undefined;
+ if (topology === 'triangle-strip' || topology === 'line-strip') {
+ stripIndexFormat = 'uint32';
+ }
+
+ // Draw a primitive using 6 vertices based on the type.
+ // Pixels are generated based on vertex position.
+ // If point, 1 pixel is generated at each vertex location.
+ // Otherwise, >1 pixels could be generated.
+ // Output color is solid green.
+ renderPass.setPipeline(
+ this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @location(0) pos : vec4<f32>
+ ) -> @builtin(position) vec4<f32> {
+ return pos;
+ }`
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ arrayStride: 4 * Float32Array.BYTES_PER_ELEMENT,
+ attributes: [
+ {
+ format: 'float32x4',
+ offset: 0,
+ shaderLocation: 0
+ }]
+
+ }]
+
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: kColorFormat }]
+ },
+ primitive: {
+ topology,
+ stripIndexFormat
+ }
+ })
+ );
+
+ // Create vertices for the primitive in a vertex buffer and bind it.
+ const vertexCoords = generateVertexBuffer(VertexLocations);
+ const vertexBuffer = this.makeBufferWithContents(vertexCoords, GPUBufferUsage.VERTEX);
+ renderPass.setVertexBuffer(0, vertexBuffer);
+
+ // Restart the strip between [v3, <restart>, v4].
+ if (primitiveRestart) {
+ const indexBuffer = this.makeBufferWithContents(
+ new Uint32Array([0, 1, 2, -1, 3, 4, 5]),
+ GPUBufferUsage.INDEX
+ );
+ renderPass.setIndexBuffer(indexBuffer, 'uint32');
+
+ if (indirect) {
+ renderPass.drawIndexedIndirect(
+ this.makeBufferWithContents(
+ new Uint32Array([drawCount + 1, 1, 0, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ ),
+ 0
+ );
+ } else {
+ renderPass.drawIndexed(drawCount + 1); // extra index for restart
+ }
+ } else {
+ if (indirect) {
+ renderPass.drawIndirect(
+ this.makeBufferWithContents(
+ new Uint32Array([drawCount, 1, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ ),
+ 0
+ );
+ } else {
+ renderPass.draw(drawCount);
+ }
+ }
+
+ renderPass.end();
+
+ this.device.queue.submit([encoder.finish()]);
+ this.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, testLocations);
+ }
+}
+
+export const g = makeTestGroup(PrimitiveTopologyTest);
+
+const topologies = [
+'point-list',
+'line-list',
+'line-strip',
+'triangle-list',
+'triangle-strip'];
+
+
+g.test('basic').
+desc(
+ `Compute test locations for valid and invalid pixels for each topology.
+ If the primitive covers the pixel, the color value will be |kValidPixelColor|.
+ Otherwise, a non-covered pixel will be |kInvalidPixelColor|.
+
+ Params:
+ - topology= {...all topologies}
+ - indirect= {true, false}
+ - primitiveRestart= { true, false } - always false for non-strip topologies
+ `
+).
+params((u) =>
+u //
+.combine('topology', topologies).
+combine('indirect', [false, true]).
+combine('primitiveRestart', [false, true]).
+unless(
+ (p) => p.primitiveRestart && p.topology !== 'line-strip' && p.topology !== 'triangle-strip'
+)
+).
+fn((t) => {
+ t.run({
+ ...t.params,
+ testLocations: getDefaultTestLocations(t.params)
+ });
+});
+
+g.test('unaligned_vertex_count').
+desc(
+ `Test that drawing with a number of vertices that's not a multiple of the vertices a given primitive list topology is not an error. The last primitive is not drawn.
+
+ Params:
+ - topology= {line-list, triangle-list}
+ - indirect= {true, false}
+ - drawCount - number of vertices to draw. A value smaller than the test's default of ${kDefaultDrawCount}.
+ One smaller for line-list. One or two smaller for triangle-list.
+ `
+).
+params((u) =>
+u //
+.combine('topology', ['line-list', 'triangle-list']).
+combine('indirect', [false, true]).
+expand('drawCount', function* (p) {
+ switch (p.topology) {
+ case 'line-list':
+ yield kDefaultDrawCount - 1;
+ break;
+ case 'triangle-list':
+ yield kDefaultDrawCount - 1;
+ yield kDefaultDrawCount - 2;
+ break;
+ }
+})
+).
+fn((t) => {
+ const testLocations = getDefaultTestLocations({ ...t.params, invalidateLastInList: true });
+ t.run({
+ ...t.params,
+ testLocations
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/sample_mask.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/sample_mask.spec.js
new file mode 100644
index 0000000000..60487998df
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/sample_mask.spec.js
@@ -0,0 +1,806 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests that the final sample mask is the logical AND of all the relevant masks, including
+the rasterization mask, sample mask, fragment output mask, and alpha to coverage mask (when alphaToCoverageEnabled === true).
+
+Also tested:
+- The positions of samples in the standard sample patterns.
+- Per-sample interpolation sampling: @interpolate(perspective, sample).
+
+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%
+- coverage is always 0%
+- it uses the first non-null attachment
+- it's an error
+Details could be found at: https://github.com/gpuweb/cts/issues/2201
+`;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 { TexelView } from '../../../util/texture/texel_view.js';
+
+const kColors = [
+// Red
+new Uint8Array([0xff, 0, 0, 0xff]),
+// Green
+new Uint8Array([0, 0xff, 0, 0xff]),
+// Blue
+new Uint8Array([0, 0, 0xff, 0xff]),
+// Yellow
+new Uint8Array([0xff, 0xff, 0, 0xff])];
+
+
+const kDepthClearValue = 1.0;
+const kDepthWriteValue = 0.0;
+const kStencilClearValue = 0;
+const kStencilReferenceValue = 0xff;
+
+// Format of the render target and resolve target
+const format = 'rgba8unorm';
+
+// Format of depth stencil attachment
+const depthStencilFormat = 'depth24plus-stencil8';
+
+const kRenderTargetSize = 1;
+
+function hasSample(
+rasterizationMask,
+sampleMask,
+fragmentShaderOutputMask,
+sampleIndex = 0)
+{
+ return (rasterizationMask & sampleMask & fragmentShaderOutputMask & 1 << sampleIndex) > 0;
+}
+
+function getExpectedColorData(
+sampleCount,
+rasterizationMask,
+sampleMask,
+fragmentShaderOutputMaskOrAlphaToCoverageMask)
+{
+ const expectedData = new Float32Array(sampleCount * 4);
+ if (sampleCount === 1) {
+ if (hasSample(rasterizationMask, sampleMask, fragmentShaderOutputMaskOrAlphaToCoverageMask)) {
+ // Texel 3 is sampled at the pixel center
+ expectedData[0] = kColors[3][0] / 0xff;
+ expectedData[1] = kColors[3][1] / 0xff;
+ expectedData[2] = kColors[3][2] / 0xff;
+ expectedData[3] = kColors[3][3] / 0xff;
+ }
+ } else {
+ for (let i = 0; i < sampleCount; i++) {
+ if (
+ hasSample(rasterizationMask, sampleMask, fragmentShaderOutputMaskOrAlphaToCoverageMask, i))
+ {
+ const o = i * 4;
+ expectedData[o + 0] = kColors[i][0] / 0xff;
+ expectedData[o + 1] = kColors[i][1] / 0xff;
+ expectedData[o + 2] = kColors[i][2] / 0xff;
+ expectedData[o + 3] = kColors[i][3] / 0xff;
+ }
+ }
+ }
+ return expectedData;
+}
+
+function getExpectedDepthData(
+sampleCount,
+rasterizationMask,
+sampleMask,
+fragmentShaderOutputMaskOrAlphaToCoverageMask)
+{
+ const expectedData = new Float32Array(sampleCount);
+ for (let i = 0; i < sampleCount; i++) {
+ const s = hasSample(
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMaskOrAlphaToCoverageMask,
+ i
+ );
+ expectedData[i] = s ? kDepthWriteValue : kDepthClearValue;
+ }
+ return expectedData;
+}
+
+function getExpectedStencilData(
+sampleCount,
+rasterizationMask,
+sampleMask,
+fragmentShaderOutputMaskOrAlphaToCoverageMask)
+{
+ const expectedData = new Uint32Array(sampleCount);
+ for (let i = 0; i < sampleCount; i++) {
+ const s = hasSample(
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMaskOrAlphaToCoverageMask,
+ i
+ );
+ expectedData[i] = s ? kStencilReferenceValue : kStencilClearValue;
+ }
+ return expectedData;
+}
+
+const kSampleMaskTestShader = `
+struct Varyings {
+ @builtin(position) Position : vec4<f32>,
+ @location(0) @interpolate(flat) uvFlat : vec2<f32>,
+ @location(1) @interpolate(perspective, sample) uvInterpolated : vec2<f32>,
+}
+
+//
+// Vertex shader
+//
+
+@vertex
+fn vmain(@builtin(vertex_index) VertexIndex : u32,
+ @builtin(instance_index) InstanceIndex : u32) -> Varyings {
+ // Standard sample locations within a pixel, where the pixel ranges from (-1,-1) to (1,1), and is
+ // centered at (0,0) (NDC - the test uses a 1x1 render target).
+ // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
+ var sampleCenters = array(
+ // sampleCount = 1
+ vec2f(0, 0),
+ // sampleCount = 4
+ vec2f(-2, 6) / 8,
+ vec2f( 6, 2) / 8,
+ vec2f(-6, -2) / 8,
+ vec2f( 2, -6) / 8,
+ );
+ // A tiny quad to draw around the sample center to ensure we hit only the expected point.
+ let kTinyQuadRadius = 1.0 / 32;
+ var tinyQuad = array(
+ vec2f( kTinyQuadRadius, kTinyQuadRadius),
+ vec2f( kTinyQuadRadius, -kTinyQuadRadius),
+ vec2f(-kTinyQuadRadius, -kTinyQuadRadius),
+ vec2f( kTinyQuadRadius, kTinyQuadRadius),
+ vec2f(-kTinyQuadRadius, -kTinyQuadRadius),
+ vec2f(-kTinyQuadRadius, kTinyQuadRadius),
+ );
+
+ var uvsFlat = array(
+ // sampleCount = 1
+ // Note: avoids hitting the point between the 4 texels.
+ vec2f(0.51, 0.51),
+ // sampleCount = 4
+ vec2f(0.25, 0.25),
+ vec2f(0.75, 0.25),
+ vec2f(0.25, 0.75),
+ vec2f(0.75, 0.75),
+ );
+ var uvsInterpolated = array(
+ // center quad
+ // Note: the interpolated point will be exactly in the middle of the 4 texels.
+ // The test expects to get texel 1,1 (the 3rd texel) in this case.
+ vec2f(1.0, 0.0),
+ vec2f(1.0, 1.0),
+ vec2f(0.0, 1.0),
+ vec2f(1.0, 0.0),
+ vec2f(0.0, 1.0),
+ vec2f(0.0, 0.0),
+
+ // top-left quad (texel 0)
+ vec2f(0.5, 0.0),
+ vec2f(0.5, 0.5),
+ vec2f(0.0, 0.5),
+ vec2f(0.5, 0.0),
+ vec2f(0.0, 0.5),
+ vec2f(0.0, 0.0),
+
+ // top-right quad (texel 1)
+ vec2f(1.0, 0.0),
+ vec2f(1.0, 0.5),
+ vec2f(0.5, 0.5),
+ vec2f(1.0, 0.0),
+ vec2f(0.5, 0.5),
+ vec2f(0.5, 0.0),
+
+ // bottom-left quad (texel 2)
+ vec2f(0.5, 0.5),
+ vec2f(0.5, 1.0),
+ vec2f(0.0, 1.0),
+ vec2f(0.5, 0.5),
+ vec2f(0.0, 1.0),
+ vec2f(0.0, 0.5),
+
+ // bottom-right quad (texel 3)
+ vec2f(1.0, 0.5),
+ vec2f(1.0, 1.0),
+ vec2f(0.5, 1.0),
+ vec2f(1.0, 0.5),
+ vec2f(0.5, 1.0),
+ vec2f(0.5, 0.5)
+ );
+
+ var output : Varyings;
+ let pos = sampleCenters[InstanceIndex] + tinyQuad[VertexIndex];
+ output.Position = vec4(pos, ${kDepthWriteValue}, 1.0);
+ output.uvFlat = uvsFlat[InstanceIndex];
+ output.uvInterpolated = uvsInterpolated[InstanceIndex * 6 + VertexIndex];
+ return output;
+}
+
+//
+// Fragment shaders
+//
+
+@group(0) @binding(0) var mySampler: sampler;
+@group(0) @binding(1) var myTexture: texture_2d<f32>;
+
+// For test named 'fragment_output_mask'
+
+@group(0) @binding(2) var<uniform> fragMask: u32;
+struct FragmentOutput1 {
+ @builtin(sample_mask) mask : u32,
+ @location(0) color : vec4<f32>,
+}
+@fragment fn fmain__fragment_output_mask__flat(varyings: Varyings) -> FragmentOutput1 {
+ return FragmentOutput1(fragMask, textureSample(myTexture, mySampler, varyings.uvFlat));
+}
+@fragment fn fmain__fragment_output_mask__interp(varyings: Varyings) -> FragmentOutput1 {
+ return FragmentOutput1(fragMask, textureSample(myTexture, mySampler, varyings.uvInterpolated));
+}
+
+// For test named 'alpha_to_coverage_mask'
+
+struct FragmentOutput2 {
+ @location(0) color0 : vec4<f32>,
+ @location(1) color1 : vec4<f32>,
+}
+@group(0) @binding(2) var<uniform> alpha: vec2<f32>;
+@fragment fn fmain__alpha_to_coverage_mask__flat(varyings: Varyings) -> FragmentOutput2 {
+ var c = textureSample(myTexture, mySampler, varyings.uvFlat);
+ return FragmentOutput2(vec4(c.xyz, alpha[0]), vec4(c.xyz, alpha[1]));
+}
+@fragment fn fmain__alpha_to_coverage_mask__interp(varyings: Varyings) -> FragmentOutput2 {
+ var c = textureSample(myTexture, mySampler, varyings.uvInterpolated);
+ return FragmentOutput2(vec4(c.xyz, alpha[0]), vec4(c.xyz, alpha[1]));
+}
+`;
+
+class F extends TextureTestMixin(GPUTest) {
+
+
+
+ async init() {
+ await super.init();
+ if (this.isCompatibility) {
+ this.skip('WGSL sample_mask is not supported in compatibility mode');
+ }
+ // Create a 2x2 color texture to sample from
+ // texel 0 - Red
+ // texel 1 - Green
+ // texel 2 - Blue
+ // texel 3 - Yellow
+ const kSampleTextureSize = 2;
+ this.sampleTexture = this.createTextureFromTexelView(
+ TexelView.fromTexelsAsBytes(format, (coord) => {
+ const id = coord.x + coord.y * kSampleTextureSize;
+ return kColors[id];
+ }),
+ {
+ size: [kSampleTextureSize, kSampleTextureSize, 1],
+ usage:
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.RENDER_ATTACHMENT
+ }
+ );
+
+ this.sampler = this.device.createSampler({
+ magFilter: 'nearest',
+ minFilter: 'nearest'
+ });
+ }
+
+ GetTargetTexture(
+ sampleCount,
+ rasterizationMask,
+ pipeline,
+ uniformBuffer,
+ colorTargetsCount = 1)
+ {
+ assert(this.sampleTexture !== undefined);
+ assert(this.sampler !== undefined);
+
+ const uniformBindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: this.sampler
+ },
+ {
+ binding: 1,
+ resource: this.sampleTexture.createView()
+ },
+ {
+ binding: 2,
+ resource: {
+ buffer: uniformBuffer
+ }
+ }]
+
+ });
+
+ const renderTargetTextures = [];
+ const resolveTargetTextures = [];
+ for (let i = 0; i < colorTargetsCount; i++) {
+ const renderTargetTexture = this.device.createTexture({
+ format,
+ size: {
+ width: kRenderTargetSize,
+ height: kRenderTargetSize,
+ depthOrArrayLayers: 1
+ },
+ sampleCount,
+ mipLevelCount: 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
+ });
+ renderTargetTextures.push(renderTargetTexture);
+
+ const resolveTargetTexture =
+ sampleCount === 1 ?
+ null :
+ this.device.createTexture({
+ format,
+ size: {
+ width: kRenderTargetSize,
+ height: kRenderTargetSize,
+ depthOrArrayLayers: 1
+ },
+ sampleCount: 1,
+ mipLevelCount: 1,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ resolveTargetTextures.push(resolveTargetTexture);
+ }
+
+ const depthStencilTexture = this.device.createTexture({
+ size: {
+ width: kRenderTargetSize,
+ height: kRenderTargetSize
+ },
+ format: depthStencilFormat,
+ sampleCount,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ const renderPassDescriptor = {
+ colorAttachments: renderTargetTextures.map((renderTargetTexture, index) => {
+ return {
+ view: renderTargetTexture.createView(),
+ resolveTarget: resolveTargetTextures[index]?.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ };
+ }),
+ depthStencilAttachment: {
+ view: depthStencilTexture.createView(),
+ depthClearValue: kDepthClearValue,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilClearValue: kStencilClearValue,
+ stencilLoadOp: 'clear',
+ stencilStoreOp: 'store'
+ }
+ };
+ const commandEncoder = this.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+ passEncoder.setPipeline(pipeline);
+ passEncoder.setBindGroup(0, uniformBindGroup);
+ passEncoder.setStencilReference(kStencilReferenceValue);
+
+ if (sampleCount === 1) {
+ if ((rasterizationMask & 1) !== 0) {
+ // draw center quad
+ passEncoder.draw(6, 1, 0, 0);
+ }
+ } else {
+ assert(sampleCount === 4);
+ if ((rasterizationMask & 1) !== 0) {
+ // draw top-left quad
+ passEncoder.draw(6, 1, 0, 1);
+ }
+ if ((rasterizationMask & 2) !== 0) {
+ // draw top-right quad
+ passEncoder.draw(6, 1, 0, 2);
+ }
+ if ((rasterizationMask & 4) !== 0) {
+ // draw bottom-left quad
+ passEncoder.draw(6, 1, 0, 3);
+ }
+ if ((rasterizationMask & 8) !== 0) {
+ // draw bottom-right quad
+ passEncoder.draw(6, 1, 0, 4);
+ }
+ }
+ passEncoder.end();
+ this.device.queue.submit([commandEncoder.finish()]);
+
+ return {
+ color: renderTargetTextures[0],
+ depthStencil: depthStencilTexture
+ };
+ }
+
+ CheckColorAttachmentResult(
+ texture,
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask)
+ {
+ const buffer = this.copySinglePixelTextureToBufferUsingComputePass(
+ TypeF32, // correspond to 'rgba8unorm' format
+ 4,
+ texture.createView(),
+ sampleCount
+ );
+
+ const expected = getExpectedColorData(
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask
+ );
+ this.expectGPUBufferValuesEqual(buffer, expected);
+ }
+
+ CheckDepthStencilResult(
+ aspect,
+ depthStencilTexture,
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask)
+ {
+ const buffer = this.copySinglePixelTextureToBufferUsingComputePass(
+ // Use f32 as the scalar type for depth (depth24plus, depth32float)
+ // Use u32 as the scalar type for stencil (stencil8)
+ aspect === 'depth-only' ? TypeF32 : TypeU32,
+ 1,
+ depthStencilTexture.createView({ aspect }),
+ sampleCount
+ );
+
+ const expected =
+ aspect === 'depth-only' ?
+ getExpectedDepthData(sampleCount, rasterizationMask, sampleMask, fragmentShaderOutputMask) :
+ getExpectedStencilData(
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask
+ );
+ this.expectGPUBufferValuesEqual(buffer, expected);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('fragment_output_mask').
+desc(
+ `
+Tests that the final sample mask is the logical AND of all the relevant masks -- meaning that the samples
+not included in the final mask are discarded on any attachments including
+- color outputs
+- depth tests
+- stencil operations
+
+The test draws 0/1/1+ textured quads of which each sample in the standard 4-sample pattern results in a different color:
+- Sample 0, Texel 0, top-left: Red
+- Sample 1, Texel 1, top-left: Green
+- Sample 2, Texel 2, top-left: Blue
+- Sample 3, Texel 3, top-left: Yellow
+
+The test checks each sample value of the render target texture and depth stencil texture using a compute pass to
+textureLoad each sample index from the texture and write to a storage buffer to compare with expected values.
+
+- for sampleCount = { 1, 4 } and various combinations of:
+ - rasterization mask = { 0, ..., 2 ** sampleCount - 1 }
+ - sample mask = { 0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110 }
+ - fragment shader output @builtin(sample_mask) = { 0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110 }
+- [choosing 0b11110 because the 5th bit should be ignored]
+`
+).
+params((u) =>
+u.
+combine('interpolated', [false, true]).
+combine('sampleCount', [1, 4]).
+expand('rasterizationMask', function* (p) {
+ const maxMask = 2 ** p.sampleCount - 1;
+ for (let i = 0; i <= maxMask; i++) {
+ yield i;
+ }
+}).
+beginSubcases().
+combine('sampleMask', [
+0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110]
+).
+combine('fragmentShaderOutputMask', [
+0, 0b0001, 0b0010, 0b0111, 0b1011, 0b1101, 0b1110, 0b1111, 0b11110]
+)
+).
+fn((t) => {
+ const { sampleCount, rasterizationMask, sampleMask, fragmentShaderOutputMask } = t.params;
+
+ const fragmentMaskUniformBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(fragmentMaskUniformBuffer);
+ t.device.queue.writeBuffer(
+ fragmentMaskUniformBuffer,
+ 0,
+ new Uint32Array([fragmentShaderOutputMask])
+ );
+
+ const module = t.device.createShaderModule({ code: kSampleMaskTestShader });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vmain' },
+ fragment: {
+ module,
+ entryPoint: `fmain__fragment_output_mask__${t.params.interpolated ? 'interp' : 'flat'}`,
+ targets: [{ format }]
+ },
+ primitive: { topology: 'triangle-list' },
+ multisample: {
+ count: sampleCount,
+ mask: sampleMask,
+ alphaToCoverageEnabled: false
+ },
+ depthStencil: {
+ format: depthStencilFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+
+ stencilFront: {
+ compare: 'always',
+ passOp: 'replace'
+ },
+ stencilBack: {
+ compare: 'always',
+ passOp: 'replace'
+ }
+ }
+ });
+
+ const { color, depthStencil } = t.GetTargetTexture(
+ sampleCount,
+ rasterizationMask,
+ pipeline,
+ fragmentMaskUniformBuffer
+ );
+
+ t.CheckColorAttachmentResult(
+ color,
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask
+ );
+
+ t.CheckDepthStencilResult(
+ 'depth-only',
+ depthStencil,
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask
+ );
+
+ t.CheckDepthStencilResult(
+ 'stencil-only',
+ depthStencil,
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ fragmentShaderOutputMask
+ );
+});
+
+g.test('alpha_to_coverage_mask').
+desc(
+ `
+Test that alpha_to_coverage_mask is working properly with the alpha output of color target[0].
+
+- for sampleCount = 4, alphaToCoverageEnabled = true and various combinations of:
+ - rasterization masks
+ - increasing alpha0 values of the color0 output including { < 0, = 0, = 1/16, = 2/16, ..., = 15/16, = 1, > 1 }
+ - alpha1 values of the color1 output = { 0, 0.5, 1.0 }.
+- test that for a single pixel in { color0, color1 } { color0, depth, stencil } output the final sample mask is applied to it, moreover:
+ - if alpha0 is 0.0 or less then alpha to coverage mask is 0x0,
+ - if alpha0 is 1.0 or greater then alpha to coverage mask is 0xFFFFFFFF,
+ - that the number of bits in the alpha to coverage mask is non-decreasing,
+ - that the computation of alpha to coverage mask doesn't depend on any other color output than color0,
+ - (not included in the spec): that once a sample is included in the alpha to coverage sample mask
+ it will be included for any alpha greater than or equal to the current value.
+
+The algorithm of producing the alpha-to-coverage mask is platform-dependent. The test draws a different color
+at each sample point. for any two alpha values (alpha and alpha') where 0 < alpha' < alpha < 1, the color values (color and color') must satisfy
+color' <= color.
+`
+).
+params((u) =>
+u.
+combine('interpolated', [false, true]).
+combine('sampleCount', [4]).
+expand('rasterizationMask', function* (p) {
+ const maxMask = 2 ** p.sampleCount - 1;
+ for (let i = 0; i <= maxMask; i++) {
+ yield i;
+ }
+}).
+beginSubcases().
+combine('alpha1', [0.0, 0.5, 1.0])
+).
+fn(async (t) => {
+ const { sampleCount, rasterizationMask, alpha1 } = t.params;
+ const sampleMask = 0xffffffff;
+
+ const alphaValues = new Float32Array(4); // [alpha0, alpha1, 0, 0]
+ const alphaValueUniformBuffer = t.device.createBuffer({
+ size: alphaValues.byteLength,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(alphaValueUniformBuffer);
+
+ const module = t.device.createShaderModule({ code: kSampleMaskTestShader });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vmain' },
+ fragment: {
+ module,
+ entryPoint: `fmain__alpha_to_coverage_mask__${t.params.interpolated ? 'interp' : 'flat'}`,
+ targets: [{ format }, { format }]
+ },
+ primitive: { topology: 'triangle-list' },
+ multisample: {
+ count: sampleCount,
+ mask: sampleMask,
+ alphaToCoverageEnabled: true
+ },
+ depthStencil: {
+ format: depthStencilFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+
+ stencilFront: {
+ compare: 'always',
+ passOp: 'replace'
+ },
+ stencilBack: {
+ compare: 'always',
+ passOp: 'replace'
+ }
+ }
+ });
+
+ // { < 0, = 0, = 1/16, = 2/16, ..., = 15/16, = 1, > 1 }
+ const alpha0ParamsArray = [-0.1, ...range(16, (i) => i / 16), 1.0, 1.1];
+
+ const colorResultPromises = [];
+ const depthResultPromises = [];
+ const stencilResultPromises = [];
+
+ for (const alpha0 of alpha0ParamsArray) {
+ alphaValues[0] = alpha0;
+ alphaValues[1] = alpha1;
+ t.device.queue.writeBuffer(alphaValueUniformBuffer, 0, alphaValues);
+
+ const { color, depthStencil } = t.GetTargetTexture(
+ sampleCount,
+ rasterizationMask,
+ pipeline,
+ alphaValueUniformBuffer,
+ 2
+ );
+
+ const colorBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
+ TypeF32, // correspond to 'rgba8unorm' format
+ 4,
+ color.createView(),
+ sampleCount
+ );
+ const colorResult = t.readGPUBufferRangeTyped(colorBuffer, {
+ type: Float32Array,
+ typedLength: colorBuffer.size / Float32Array.BYTES_PER_ELEMENT
+ });
+ colorResultPromises.push(colorResult);
+
+ const depthBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
+ TypeF32, // correspond to 'depth24plus-stencil8' format
+ 1,
+ depthStencil.createView({ aspect: 'depth-only' }),
+ sampleCount
+ );
+ const depthResult = t.readGPUBufferRangeTyped(depthBuffer, {
+ type: Float32Array,
+ typedLength: depthBuffer.size / Float32Array.BYTES_PER_ELEMENT
+ });
+ depthResultPromises.push(depthResult);
+
+ const stencilBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
+ TypeU32, // correspond to 'depth24plus-stencil8' format
+ 1,
+ depthStencil.createView({ aspect: 'stencil-only' }),
+ sampleCount
+ );
+ const stencilResult = t.readGPUBufferRangeTyped(stencilBuffer, {
+ type: Uint32Array,
+ typedLength: stencilBuffer.size / Uint32Array.BYTES_PER_ELEMENT
+ });
+ stencilResultPromises.push(stencilResult);
+ }
+
+ const resultsArray = await Promise.all([
+ Promise.all(colorResultPromises),
+ Promise.all(depthResultPromises),
+ Promise.all(stencilResultPromises)]
+ );
+
+ const checkResults = (
+ results,
+ getExpectedDataFn,
+
+
+
+
+
+
+
+
+ positiveCorrelation) =>
+ {
+ for (let i = 0; i < results.length; i++) {
+ const result = results[i];
+ const alpha0 = alpha0ParamsArray[i];
+
+ if (alpha0 <= 0) {
+ const expected = getExpectedDataFn(sampleCount, rasterizationMask, sampleMask, 0x0);
+ const check = checkElementsEqual(result.data, expected);
+ t.expectOK(check);
+ } else if (alpha0 >= 1) {
+ const expected = getExpectedDataFn(
+ sampleCount,
+ rasterizationMask,
+ sampleMask,
+ 0xffffffff
+ );
+ const check = checkElementsEqual(result.data, expected);
+ t.expectOK(check);
+ } else {
+ assert(i > 0);
+ const prevResult = results[i - 1];
+ const check = checkElementsPassPredicate(
+ result.data,
+ (index, value) =>
+ positiveCorrelation ?
+ value >= prevResult.data[index] :
+ value <= prevResult.data[index],
+ {}
+ );
+ t.expectOK(check);
+ }
+ }
+
+ for (const result of results) {
+ result.cleanup();
+ }
+ };
+
+ // Check color results
+ checkResults(resultsArray[0], getExpectedColorData, true);
+
+ // Check depth results
+ checkResults(resultsArray[1], getExpectedDepthData, false);
+
+ // Check stencil results
+ checkResults(resultsArray[2], getExpectedStencilData, true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.js
new file mode 100644
index 0000000000..34a61ca27c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.js
@@ -0,0 +1,29 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test vertex-only render pipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+class F extends GPUTest {}
+
+export const g = makeTestGroup(F);
+
+g.test('draw_depth_and_stencil_with_vertex_only_pipeline').
+desc(
+ `
+TODO:
+- Test drawing depth and stencil with vertex-only render pipelines by
+ 1. Create a color attachment and depth-stencil attachment of 4 pixels in a line, clear the color
+ to RGBA(0.0, 0.0, 0.0, 0.0), depth to 0.0 and stencil to 0x0
+ 2. Use a depth and stencil test disabled vertex-only render pipeline to modify the depth of middle
+ 2 pixels to 0.5, while leaving stencil unchanged
+ 3. Use another depth and stencil test disabled vertex-only render pipeline to modify the stencil
+ of right 2 pixels to 0x1, while leaving depth unchanged
+ 4. Use a complete render pipeline to draw all 4 pixels with color RGBA(0.0, 1.0, 0.0, 1.0), but
+ with depth test requiring depth no less than 0.5 and stencil test requiring stencil equals to 0x1
+ 5. Validate that only the third pixel is of color RGBA(0.0, 1.0, 0.0, 1.0), and all other pixels
+ are RGBA(0.0, 0.0, 0.0, 0.0).
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/basic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/basic.spec.js
new file mode 100644
index 0000000000..c549a195a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/basic.spec.js
@@ -0,0 +1,353 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Basic command buffer rendering tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { now } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('clear').fn((t) => {
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const colorAttachment = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
+});
+
+g.test('fullscreen_quad').fn((t) => {
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const colorAttachment = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -3.0),
+ vec2<f32>(3.0, 1.0),
+ vec2<f32>(-1.0, 1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ storeOp: 'store',
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
+});
+
+g.test('large_draw').
+desc(
+ `Test reasonably-sized large {draw, drawIndexed} (see also stress tests).
+
+ Tests that draw calls behave reasonably with large vertex counts for
+ non-indexed draws, large index counts for indexed draws, and large instance
+ counts in both cases. Various combinations of these counts are tested with
+ both direct and indirect draw calls.
+
+ Draw call sizes are increased incrementally over these parameters until we the
+ run out of values or completion of a draw call exceeds a fixed time limit of
+ 100ms.
+
+ To validate that the drawn vertices actually made it though the pipeline on
+ each draw call, we render a 3x3 target with the positions of the first and
+ last vertices of the first and last instances in different respective corners,
+ and everything else positioned to cover only one of the intermediate
+ fragments. If the output image is completely yellow, then we can reasonably
+ infer that all vertices were drawn.
+
+ Params:
+ - indexed= {true, false} - whether to test indexed or non-indexed draw calls
+ - indirect= {true, false} - whether to use indirect or direct draw calls`
+).
+params((u) =>
+u //
+.combine('indexed', [true, false]).
+combine('indirect', [true, false])
+).
+fn(async (t) => {
+ const { indexed, indirect } = t.params;
+
+ const kBytesPerRow = 256;
+ const dst = t.device.createBuffer({
+ size: 3 * kBytesPerRow,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const paramsBuffer = t.device.createBuffer({
+ size: 8,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
+ });
+
+ const indirectBuffer = t.device.createBuffer({
+ size: 20,
+ usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST
+ });
+ const writeIndirectParams = (count, instanceCount) => {
+ const params = new Uint32Array(5);
+ params[0] = count; // Vertex or index count
+ params[1] = instanceCount;
+ params[2] = 0; // First vertex or index
+ params[3] = 0; // First instance (non-indexed) or base vertex (indexed)
+ params[4] = 0; // First instance (indexed)
+ t.device.queue.writeBuffer(indirectBuffer, 0, params, 0, 5);
+ };
+
+ let indexBuffer = null;
+ if (indexed) {
+ const kMaxIndices = 16 * 1024 * 1024;
+ indexBuffer = t.device.createBuffer({
+ size: kMaxIndices * Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(indexBuffer);
+ const indexData = new Uint32Array(indexBuffer.getMappedRange());
+ for (let i = 0; i < kMaxIndices; ++i) {
+ indexData[i] = i;
+ }
+ indexBuffer.unmap();
+ }
+
+ const colorAttachment = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 3, height: 3, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const bgLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: bgLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: paramsBuffer }
+ }]
+
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: t.device.createPipelineLayout({ bindGroupLayouts: [bgLayout] }),
+
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ struct Params {
+ numVertices: u32,
+ numInstances: u32,
+ };
+
+ fn selectValue(index: u32, maxIndex: u32) -> f32 {
+ let highOrMid = select(0.0, 2.0 / 3.0, index == maxIndex - 1u);
+ return select(highOrMid, -2.0 / 3.0, index == 0u);
+ }
+
+ @group(0) @binding(0) var<uniform> params: Params;
+
+ @vertex fn main(
+ @builtin(vertex_index) v: u32,
+ @builtin(instance_index) i: u32)
+ -> @builtin(position) vec4<f32> {
+ let x = selectValue(v, params.numVertices);
+ let y = -selectValue(i, params.numInstances);
+ return vec4<f32>(x, y, 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 1.0, 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+
+ const runPipeline = (numVertices, numInstances) => {
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ storeOp: 'store',
+ clearValue: { r: 0.0, g: 0.0, b: 1.0, a: 1.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ if (indexBuffer !== null) {
+ pass.setIndexBuffer(indexBuffer, 'uint32');
+ }
+
+ if (indirect) {
+ writeIndirectParams(numVertices, numInstances);
+ if (indexed) {
+ pass.drawIndexedIndirect(indirectBuffer, 0);
+ } else {
+ pass.drawIndirect(indirectBuffer, 0);
+ }
+ } else {
+ if (indexed) {
+ pass.drawIndexed(numVertices, numInstances);
+ } else {
+ pass.draw(numVertices, numInstances);
+ }
+ }
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: kBytesPerRow },
+ { width: 3, height: 3, depthOrArrayLayers: 1 }
+ );
+
+ const params = new Uint32Array([numVertices, numInstances]);
+ t.device.queue.writeBuffer(paramsBuffer, 0, params, 0, 2);
+ t.device.queue.submit([encoder.finish()]);
+
+ const yellow = [0xff, 0xff, 0x00, 0xff];
+ const allYellow = new Uint8Array([...yellow, ...yellow, ...yellow]);
+ for (const row of [0, 1, 2]) {
+ t.expectGPUBufferValuesPassCheck(dst, (data) => checkElementsEqual(data, allYellow), {
+ srcByteOffset: row * 256,
+ type: Uint8Array,
+ typedLength: 12
+ });
+ }
+ };
+
+ // If any iteration takes longer than this, we stop incrementing along that
+ // branch and move on to the next instance count. Note that the max
+ // supported vertex count for any iteration is 2**24 due to our choice of
+ // index buffer size.
+ const maxDurationMs = 100;
+ const counts = [
+ {
+ numInstances: 4,
+ vertexCounts: [2 ** 10, 2 ** 16, 2 ** 18, 2 ** 20, 2 ** 22, 2 ** 24]
+ },
+ {
+ numInstances: 2 ** 8,
+ vertexCounts: [2 ** 10, 2 ** 16, 2 ** 18, 2 ** 20, 2 ** 22]
+ },
+ {
+ numInstances: 2 ** 10,
+ vertexCounts: [2 ** 8, 2 ** 10, 2 ** 12, 2 ** 16, 2 ** 18, 2 ** 20]
+ },
+ {
+ numInstances: 2 ** 16,
+ vertexCounts: [2 ** 4, 2 ** 8, 2 ** 10, 2 ** 12, 2 ** 14]
+ },
+ {
+ numInstances: 2 ** 20,
+ vertexCounts: [2 ** 4, 2 ** 8, 2 ** 10]
+ }];
+
+ for (const { numInstances, vertexCounts } of counts) {
+ for (const numVertices of vertexCounts) {
+ const start = now();
+ runPipeline(numVertices, numInstances);
+ await t.device.queue.onSubmittedWorkDone();
+ const duration = now() - start;
+ if (duration >= maxDurationMs) {
+ break;
+ }
+ }
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/color_target_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/color_target_state.spec.js
new file mode 100644
index 0000000000..4e0a49f507
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/color_target_state.spec.js
@@ -0,0 +1,818 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test blending results.
+
+TODO:
+- Test result for all combinations of args (make sure each case is distinguishable from others
+- Test underflow/overflow has consistent behavior
+- ?
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, 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 { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { clamp } from '../../../util/math.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+class BlendingTest extends GPUTest {
+ createRenderPipelineForTest(colorTargetState) {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ fragment: {
+ targets: [colorTargetState],
+ module: this.device.createShaderModule({
+ code: `
+ struct Params {
+ color : vec4<f32>
+ }
+ @group(0) @binding(0) var<uniform> params : Params;
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return params.color;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(3.0, -1.0),
+ vec2<f32>(-1.0, 3.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+ }
+
+ createBindGroupForTest(layout, data) {
+ return this.device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: this.makeBufferWithContents(data, GPUBufferUsage.UNIFORM)
+ }
+ }]
+
+ });
+ }
+}
+
+export const g = makeTestGroup(TextureTestMixin(BlendingTest));
+
+function mapColor(
+col,
+f)
+{
+ return {
+ r: f(col.r, 'r'),
+ g: f(col.g, 'g'),
+ b: f(col.b, 'b'),
+ a: f(col.a, 'a')
+ };
+}
+
+function computeBlendFactor(
+src,
+dst,
+blendColor,
+factor)
+{
+ switch (factor) {
+ case 'zero':
+ return { r: 0, g: 0, b: 0, a: 0 };
+ case 'one':
+ return { r: 1, g: 1, b: 1, a: 1 };
+ case 'src':
+ return { ...src };
+ case 'one-minus-src':
+ return mapColor(src, (v) => 1 - v);
+ case 'src-alpha':
+ return mapColor(src, () => src.a);
+ case 'one-minus-src-alpha':
+ return mapColor(src, () => 1 - src.a);
+ case 'dst':
+ return { ...dst };
+ case 'one-minus-dst':
+ return mapColor(dst, (v) => 1 - v);
+ case 'dst-alpha':
+ return mapColor(dst, () => dst.a);
+ case 'one-minus-dst-alpha':
+ return mapColor(dst, () => 1 - dst.a);
+ case 'src-alpha-saturated':{
+ const f = Math.min(src.a, 1 - dst.a);
+ return { r: f, g: f, b: f, a: 1 };
+ }
+ case 'constant':
+ assert(blendColor !== undefined);
+ return { ...blendColor };
+ case 'one-minus-constant':
+ assert(blendColor !== undefined);
+ return mapColor(blendColor, (v) => 1 - v);
+ default:
+ unreachable();
+ }
+}
+
+function computeBlendOperation(
+src,
+srcFactor,
+dst,
+dstFactor,
+operation)
+{
+ switch (operation) {
+ case 'add':
+ return mapColor(src, (_, k) => srcFactor[k] * src[k] + dstFactor[k] * dst[k]);
+ case 'max':
+ return mapColor(src, (_, k) => Math.max(src[k], dst[k]));
+ case 'min':
+ return mapColor(src, (_, k) => Math.min(src[k], dst[k]));
+ case 'reverse-subtract':
+ return mapColor(src, (_, k) => dstFactor[k] * dst[k] - srcFactor[k] * src[k]);
+ case 'subtract':
+ return mapColor(src, (_, k) => srcFactor[k] * src[k] - dstFactor[k] * dst[k]);
+ }
+}
+
+g.test('blending,GPUBlendComponent').
+desc(
+ `Test all combinations of parameters for GPUBlendComponent.
+
+ Tests that parameters are correctly passed to the backend API and blend computations
+ are done correctly by blending a single pixel. The test uses rgba16float as the format
+ to avoid checking clamping behavior (tested in api,operation,rendering,blending:clamp,*).
+
+ Params:
+ - component= {color, alpha} - whether to test blending the color or the alpha component.
+ - srcFactor= {...all GPUBlendFactors}
+ - dstFactor= {...all GPUBlendFactors}
+ - operation= {...all GPUBlendOperations}`
+).
+params((u) =>
+u //
+.combine('component', ['color', 'alpha']).
+combine('srcFactor', kBlendFactors).
+combine('dstFactor', kBlendFactors).
+combine('operation', kBlendOperations).
+filter((t) => {
+ if (t.operation === 'min' || t.operation === 'max') {
+ return t.srcFactor === 'one' && t.dstFactor === 'one';
+ }
+ 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 },
+{ r: 0.09, g: 0.73, b: 0.93, a: 0.81 }]
+).
+expand('blendConstant', (p) => {
+ const needsBlendConstant =
+ p.srcFactor === 'one-minus-constant' ||
+ p.srcFactor === 'constant' ||
+ p.dstFactor === 'one-minus-constant' ||
+ p.dstFactor === 'constant';
+ return needsBlendConstant ? [{ r: 0.91, g: 0.82, b: 0.73, a: 0.64 }] : [undefined];
+})
+).
+fn((t) => {
+ const textureFormat = 'rgba16float';
+ const srcColor = t.params.srcColor;
+ const dstColor = t.params.dstColor;
+ const blendConstant = t.params.blendConstant;
+
+ const srcFactor = computeBlendFactor(srcColor, dstColor, blendConstant, t.params.srcFactor);
+ const dstFactor = computeBlendFactor(srcColor, dstColor, blendConstant, t.params.dstFactor);
+
+ const expectedColor = computeBlendOperation(
+ srcColor,
+ srcFactor,
+ dstColor,
+ dstFactor,
+ t.params.operation
+ );
+
+ switch (t.params.component) {
+ case 'color':
+ expectedColor.a = srcColor.a;
+ break;
+ case 'alpha':
+ expectedColor.r = srcColor.r;
+ expectedColor.g = srcColor.g;
+ expectedColor.b = srcColor.b;
+ break;
+ }
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ fragment: {
+ targets: [
+ {
+ format: textureFormat,
+ blend: {
+ // Set both color/alpha to defaults...
+ color: {},
+ alpha: {},
+ // ... but then override the component we're testing.
+ [t.params.component]: {
+ srcFactor: t.params.srcFactor,
+ dstFactor: t.params.dstFactor,
+ operation: t.params.operation
+ }
+ }
+ }],
+
+ module: t.device.createShaderModule({
+ code: `
+struct Uniform {
+ color: vec4<f32>
+};
+@group(0) @binding(0) var<uniform> u : Uniform;
+
+@fragment fn main() -> @location(0) vec4<f32> {
+ return u.color;
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+@vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [1, 1, 1],
+ format: textureFormat
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: dstColor,
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(pipeline);
+ if (blendConstant) {
+ renderPass.setBlendConstant(blendConstant);
+ }
+ renderPass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: t.makeBufferWithContents(
+ new Float32Array([srcColor.r, srcColor.g, srcColor.b, srcColor.a]),
+ GPUBufferUsage.UNIFORM
+ )
+ }
+ }]
+
+ })
+ );
+ renderPass.draw(1);
+ renderPass.end();
+
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ t.expectSinglePixelComparisonsAreOkInTexture(
+ { texture: renderTarget },
+ [
+ {
+ coord: { x: 0, y: 0 },
+ exp: { R: expectedColor.r, G: expectedColor.g, B: expectedColor.b, A: expectedColor.a }
+ }],
+
+ { maxFractionalDiff: 0.003 }
+ );
+});
+
+const kBlendableFormats = kEncodableTextureFormats.filter((f) => {
+ const info = kTextureFormatInfo[f];
+ return info.renderable && info.sampleType === 'float';
+});
+
+g.test('blending,formats').
+desc(
+ `Test blending results works for all formats that support it, and that blending is not applied
+ for formats that do not. Blending should be done in linear space for srgb formats.`
+).
+params((u) =>
+u //
+.combine('format', kBlendableFormats)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn((t) => {
+ const { format } = t.params;
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ fragment: {
+ targets: [
+ {
+ format,
+ blend: {
+ color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
+ alpha: { srcFactor: 'one', dstFactor: 'one', operation: 'add' }
+ }
+ }],
+
+ module: t.device.createShaderModule({
+ code: `
+@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.4, 0.4, 0.4, 0.4);
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+@vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [1, 1, 1],
+ format
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: { r: 0.2, g: 0.2, b: 0.2, a: 0.2 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(pipeline);
+ renderPass.draw(1);
+ renderPass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ const expColor = { R: 0.6, G: 0.6, B: 0.6, A: 0.6 };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1, 1]);
+});
+
+g.test('blend_constant,initial').
+desc(`Test that the blend constant is set to [0,0,0,0] at the beginning of a pass.`).
+fn((t) => {
+ const format = 'rgba8unorm';
+ const kSize = 1;
+ const kWhiteColorData = new Float32Array([255, 255, 255, 255]);
+
+ const blendComponent = { srcFactor: 'constant', dstFactor: 'one', operation: 'add' };
+ const testPipeline = t.createRenderPipelineForTest({
+ format,
+ blend: { color: blendComponent, alpha: blendComponent }
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [kSize, kSize],
+ format
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(testPipeline);
+ renderPass.setBindGroup(
+ 0,
+ t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData)
+ );
+ renderPass.draw(3);
+ // Draw [1,1,1,1] with `src * constant + dst * 1`.
+ // The blend constant defaults to [0,0,0,0], so the result is
+ // `[1,1,1,1] * [0,0,0,0] + [0,0,0,0] * 1` = [0,0,0,0].
+ renderPass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ // Check that the initial blend constant is black(0,0,0,0) after setting testPipeline which has
+ // a white color buffer data.
+ const expColor = { R: 0, G: 0, B: 0, A: 0 };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
+ kSize,
+ kSize]
+ );
+});
+
+g.test('blend_constant,setting').
+desc(`Test that setting the blend constant to the RGBA values works at the beginning of a pass.`).
+paramsSubcasesOnly([
+{ r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
+{ r: 0.5, g: 1.0, b: 0.5, a: 0.0 },
+{ r: 0.0, g: 0.0, b: 0.0, a: 0.0 }]
+).
+fn((t) => {
+ const { r, g, b, a } = t.params;
+
+ const format = 'rgba8unorm';
+ const kSize = 1;
+ const kWhiteColorData = new Float32Array([255, 255, 255, 255]);
+
+ const blendComponent = { srcFactor: 'constant', dstFactor: 'one', operation: 'add' };
+ const testPipeline = t.createRenderPipelineForTest({
+ format,
+ blend: { color: blendComponent, alpha: blendComponent }
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [kSize, kSize],
+ format
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(testPipeline);
+ renderPass.setBlendConstant({ r, g, b, a });
+ renderPass.setBindGroup(
+ 0,
+ t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData)
+ );
+ renderPass.draw(3);
+ // Draw [1,1,1,1] with `src * constant + dst * 1`. The blend constant to [r,g,b,a], so the
+ // result is `[1,1,1,1] * [r,g,b,a] + [0,0,0,0] * 1` = [r,g,b,a].
+ renderPass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ // Check that the blend constant is the same as the given constant after setting the constant
+ // via setBlendConstant.
+ const expColor = { R: r, G: g, B: b, A: a };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
+ kSize,
+ kSize]
+ );
+});
+
+g.test('blend_constant,not_inherited').
+desc(`Test that the blending constant is not inherited between render passes.`).
+fn((t) => {
+ const format = 'rgba8unorm';
+ const kSize = 1;
+ const kWhiteColorData = new Float32Array([255, 255, 255, 255]);
+
+ const blendComponent = { srcFactor: 'constant', dstFactor: 'one', operation: 'add' };
+ const testPipeline = t.createRenderPipelineForTest({
+ format,
+ blend: { color: blendComponent, alpha: blendComponent }
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [kSize, kSize],
+ format
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ {
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(testPipeline);
+ renderPass.setBlendConstant({ r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Set to white color.
+ renderPass.setBindGroup(
+ 0,
+ t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData)
+ );
+ renderPass.draw(3);
+ // Draw [1,1,1,1] with `src * constant + dst * 1`. The blend constant to [1,1,1,1], so the
+ // result is `[1,1,1,1] * [1,1,1,1] + [0,0,0,0] * 1` = [1,1,1,1].
+ renderPass.end();
+ }
+ {
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(testPipeline);
+ renderPass.setBindGroup(
+ 0,
+ t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kWhiteColorData)
+ );
+ renderPass.draw(3);
+ // Draw [1,1,1,1] with `src * constant + dst * 1`. The blend constant defaults to [0,0,0,0],
+ // so the result is `[1,1,1,1] * [0,0,0,0] + [0,0,0,0] * 1` = [0,0,0,0].
+ renderPass.end();
+ }
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ // Check that the blend constant is not inherited from the first render pass.
+ const expColor = { R: 0, G: 0, B: 0, A: 0 };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
+ kSize,
+ kSize]
+ );
+});
+
+const kColorWriteCombinations = [
+0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+
+g.test('color_write_mask,channel_work').
+desc(
+ `
+ Test that the color write mask works with the zero channel, a single channel, multiple channels,
+ and all channels.
+ `
+).
+params((u) =>
+u //
+.combine('mask', kColorWriteCombinations)
+).
+fn((t) => {
+ const { mask } = t.params;
+
+ const format = 'rgba8unorm';
+ const kSize = 1;
+
+ let r = 0,
+ g = 0,
+ b = 0,
+ a = 0;
+ if (mask & GPUConst.ColorWrite.RED) {
+ r = 1;
+ }
+ if (mask & GPUConst.ColorWrite.GREEN) {
+ g = 1;
+ }
+ if (mask & GPUConst.ColorWrite.BLUE) {
+ b = 1;
+ }
+ if (mask & GPUConst.ColorWrite.ALPHA) {
+ a = 1;
+ }
+
+ const testPipeline = t.createRenderPipelineForTest({
+ format,
+ writeMask: mask
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [kSize, kSize],
+ format
+ });
+
+ const kBaseColorData = new Float32Array([32, 64, 128, 192]);
+
+ const commandEncoder = t.device.createCommandEncoder();
+ {
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(testPipeline);
+ renderPass.setBindGroup(
+ 0,
+ t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kBaseColorData)
+ );
+ renderPass.draw(3);
+ renderPass.end();
+ }
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ const expColor = { R: r, G: g, B: b, A: a };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
+ kSize,
+ kSize]
+ );
+});
+
+g.test('color_write_mask,blending_disabled').
+desc(
+ `Test that the color write mask works when blending is disabled or set to the defaults
+ (which has the same blending result).`
+).
+params((u) => u.combine('disabled', [false, true])).
+fn((t) => {
+ const format = 'rgba8unorm';
+ const kSize = 1;
+
+ const blend = t.params.disabled ? undefined : { color: {}, alpha: {} };
+
+ const testPipeline = t.createRenderPipelineForTest({
+ format,
+ blend,
+ writeMask: GPUColorWrite.RED
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [kSize, kSize],
+ format
+ });
+
+ const kBaseColorData = new Float32Array([32, 64, 128, 192]);
+
+ const commandEncoder = t.device.createCommandEncoder();
+ {
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(testPipeline);
+ renderPass.setBindGroup(
+ 0,
+ t.createBindGroupForTest(testPipeline.getBindGroupLayout(0), kBaseColorData)
+ );
+ // Draw [1,1,1,1] with `src * 1 + dst * 0`. So the
+ // result is `[1,1,1,1] * [1,1,1,1] + [0,0,0,0] * 0` = [1,1,1,1].
+ renderPass.draw(3);
+ renderPass.end();
+ }
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ const expColor = { R: 1, G: 0, B: 0, A: 0 };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [
+ kSize,
+ kSize]
+ );
+});
+
+g.test('blending,clamping').
+desc(
+ `
+ Test that clamping occurs at the correct points in the blend process: src value, src factor, dst
+ factor, and output.
+ - TODO: Need to test snorm formats.
+ - TODO: Need to test src value, srcFactor and dstFactor.
+ `
+).
+params((u) =>
+u //
+.combine('format', ['rgba8unorm', 'rg16float']).
+combine('srcValue', [0.4, 0.6, 0.8, 1.0]).
+combine('dstValue', [0.2, 0.4])
+).
+fn((t) => {
+ const { format, srcValue, dstValue } = t.params;
+
+ const blendComponent = { srcFactor: 'one', dstFactor: 'one', operation: 'add' };
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ fragment: {
+ targets: [
+ {
+ format,
+ blend: {
+ color: blendComponent,
+ alpha: blendComponent
+ }
+ }],
+
+ module: t.device.createShaderModule({
+ code: `
+@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(${srcValue}, ${srcValue}, ${srcValue}, ${srcValue});
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+@vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+
+ const renderTarget = t.device.createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [1, 1, 1],
+ format
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: { r: dstValue, g: dstValue, b: dstValue, a: dstValue },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(pipeline);
+ renderPass.draw(1);
+ renderPass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ let expValue;
+ switch (format) {
+ case 'rgba8unorm': // unorm types should clamp if the sum of srcValue and dstValue exceeds 1.
+ expValue = clamp(srcValue + dstValue, { min: 0, max: 1 });
+ break;
+ case 'rg16float': // float format types doesn't clamp.
+ expValue = srcValue + dstValue;
+ break;
+ }
+
+ const expColor = { R: expValue, G: expValue, B: expValue, A: expValue };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+
+ t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1, 1]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth.spec.js
new file mode 100644
index 0000000000..12c88f8dcd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth.spec.js
@@ -0,0 +1,546 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test related to depth buffer, depth op, compare func, etc.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+
+import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+const backgroundColor = [0x00, 0x00, 0x00, 0xff];
+const triangleColor = [0xff, 0xff, 0xff, 0xff];
+
+const kBaseColor = new Float32Array([1.0, 1.0, 1.0, 1.0]);
+const kRedStencilColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
+const kGreenStencilColor = new Float32Array([0.0, 1.0, 0.0, 1.0]);
+
+
+
+
+
+
+
+class DepthTest extends TextureTestMixin(GPUTest) {
+ runDepthStateTest(testStates, expectedColor) {
+ const renderTargetFormat = 'rgba8unorm';
+
+ const renderTarget = this.trackForCleanup(
+ this.device.createTexture({
+ format: renderTargetFormat,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const depthStencilFormat = 'depth24plus-stencil8';
+ const depthTexture = this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: depthStencilFormat,
+ sampleCount: 1,
+ mipLevelCount: 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST
+ })
+ );
+
+ const depthStencilAttachment = {
+ view: depthTexture.createView(),
+ depthLoadOp: 'load',
+ depthStoreOp: 'store',
+ stencilLoadOp: 'load',
+ stencilStoreOp: 'store'
+ };
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ loadOp: 'load'
+ }],
+
+ depthStencilAttachment
+ });
+
+ // Draw a triangle with the given depth state, color, and depth.
+ for (const test of testStates) {
+ const testPipeline = this.createRenderPipelineForTest(test.state, test.depth);
+ pass.setPipeline(testPipeline);
+ pass.setBindGroup(
+ 0,
+ this.createBindGroupForTest(testPipeline.getBindGroupLayout(0), test.color)
+ );
+ pass.draw(1);
+ }
+
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ const expColor = {
+ R: expectedColor[0],
+ G: expectedColor[1],
+ B: expectedColor[2],
+ A: expectedColor[3]
+ };
+ const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, (_coords) => expColor);
+
+ this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
+ }
+
+ createRenderPipelineForTest(
+ depthStencil,
+ depth)
+ {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, ${depth}, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ targets: [{ format: 'rgba8unorm' }],
+ module: this.device.createShaderModule({
+ code: `
+ struct Params {
+ color : vec4<f32>
+ }
+ @group(0) @binding(0) var<uniform> params : Params;
+
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(params.color);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ primitive: { topology: 'point-list' },
+ depthStencil
+ });
+ }
+
+ createBindGroupForTest(layout, data) {
+ return this.device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: this.makeBufferWithContents(data, GPUBufferUsage.UNIFORM)
+ }
+ }]
+
+ });
+ }
+}
+
+export const g = makeTestGroup(DepthTest);
+
+g.test('depth_disabled').
+desc('Tests render results with depth test disabled.').
+fn((t) => {
+ const depthSpencilFormat = 'depth24plus-stencil8';
+ const state = {
+ format: depthSpencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'always'
+ };
+
+ const testStates = [
+ { state, color: kBaseColor, depth: 0.0 },
+ { state, color: kRedStencilColor, depth: 0.5 },
+ { state, color: kGreenStencilColor, depth: 1.0 }];
+
+
+ // Test that for all combinations and ensure the last triangle drawn is the one visible
+ // regardless of depth testing.
+ for (let last = 0; last < 3; ++last) {
+ const i = (last + 1) % 3;
+ const j = (last + 2) % 3;
+
+ t.runDepthStateTest([testStates[i], testStates[j], testStates[last]], testStates[last].color);
+ t.runDepthStateTest([testStates[j], testStates[i], testStates[last]], testStates[last].color);
+ }
+});
+
+g.test('depth_write_disabled').
+desc(
+ `
+ Test that depthWriteEnabled behaves as expected.
+ If enabled, a depth value of 0.0 is written.
+ If disabled, it's not written, so it keeps the previous value of 1.0.
+ Use a depthCompare: 'equal' check at the end to check the value.
+ `
+).
+params((u) =>
+u //
+.combineWithParams([
+{ depthWriteEnabled: false, lastDepth: 0.0, _expectedColor: kRedStencilColor },
+{ depthWriteEnabled: true, lastDepth: 0.0, _expectedColor: kGreenStencilColor },
+{ depthWriteEnabled: false, lastDepth: 1.0, _expectedColor: kGreenStencilColor },
+{ depthWriteEnabled: true, lastDepth: 1.0, _expectedColor: kRedStencilColor }]
+)
+).
+fn((t) => {
+ const { depthWriteEnabled, lastDepth, _expectedColor } = t.params;
+
+ const depthSpencilFormat = 'depth24plus-stencil8';
+
+ const stencilState = {
+ compare: 'always',
+ failOp: 'keep',
+ depthFailOp: 'keep',
+ passOp: 'keep'
+ };
+
+ const baseState = {
+ format: depthSpencilFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+ stencilFront: stencilState,
+ stencilBack: stencilState,
+ stencilReadMask: 0xff,
+ stencilWriteMask: 0xff
+ };
+
+ const depthWriteState = {
+ format: depthSpencilFormat,
+ depthWriteEnabled,
+ depthCompare: 'always',
+ stencilFront: stencilState,
+ stencilBack: stencilState,
+ stencilReadMask: 0xff,
+ stencilWriteMask: 0xff
+ };
+
+ const checkState = {
+ format: depthSpencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'equal',
+ stencilFront: stencilState,
+ stencilBack: stencilState,
+ stencilReadMask: 0xff,
+ stencilWriteMask: 0xff
+ };
+
+ const testStates = [
+ // Draw a base point with depth write enabled.
+ { state: baseState, color: kBaseColor, depth: 1.0 },
+ // Draw a second point without depth write enabled.
+ { state: depthWriteState, color: kRedStencilColor, depth: 0.0 },
+ // Draw a third point which should occlude the second even though it is behind it.
+ { state: checkState, color: kGreenStencilColor, depth: lastDepth }];
+
+
+ t.runDepthStateTest(testStates, _expectedColor);
+});
+
+g.test('depth_test_fail').
+desc(
+ `
+ Test that render results on depth test failure cases with 'less' depthCompare operation and
+ depthWriteEnabled is true.
+ `
+).
+params((u) =>
+u //
+.combineWithParams([
+{ secondDepth: 1.0, lastDepth: 2.0, _expectedColor: kBaseColor }, // fail -> fail.
+{ secondDepth: 0.0, lastDepth: 2.0, _expectedColor: kRedStencilColor }, // pass -> fail.
+{ secondDepth: 2.0, lastDepth: 0.9, _expectedColor: kGreenStencilColor } // fail -> pass.
+])
+).
+fn((t) => {
+ const { secondDepth, lastDepth, _expectedColor } = t.params;
+
+ const depthSpencilFormat = 'depth24plus-stencil8';
+
+ const baseState = {
+ format: depthSpencilFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+ stencilReadMask: 0xff,
+ stencilWriteMask: 0xff
+ };
+
+ const depthTestState = {
+ format: depthSpencilFormat,
+ depthWriteEnabled: true,
+ depthCompare: 'less',
+ stencilReadMask: 0xff,
+ stencilWriteMask: 0xff
+ };
+
+ const testStates = [
+ { state: baseState, color: kBaseColor, depth: 1.0 },
+ { state: depthTestState, color: kRedStencilColor, depth: secondDepth },
+ { state: depthTestState, color: kGreenStencilColor, depth: lastDepth }];
+
+
+ t.runDepthStateTest(testStates, _expectedColor);
+});
+
+// Use a depth value that's not exactly 0.5 because it is exactly between two depth16unorm value and
+// can get rounded either way (and a different way between shaders and clearDepthValue).
+const kMiddleDepthValue = 0.5001;
+
+g.test('depth_compare_func').
+desc(
+ `Tests each depth compare function works properly. Clears the depth attachment to various values, and renders a point at depth 0.5 with various depthCompare modes.`
+).
+params((u) =>
+u.
+combine(
+ 'format',
+ kDepthStencilFormats.filter((format) => kTextureFormatInfo[format].depth)
+).
+combineWithParams([
+{ depthCompare: 'never', depthClearValue: 1.0, _expected: backgroundColor },
+{ depthCompare: 'never', depthClearValue: kMiddleDepthValue, _expected: backgroundColor },
+{ depthCompare: 'never', depthClearValue: 0.0, _expected: backgroundColor },
+{ depthCompare: 'less', depthClearValue: 1.0, _expected: triangleColor },
+{ depthCompare: 'less', depthClearValue: kMiddleDepthValue, _expected: backgroundColor },
+{ depthCompare: 'less', depthClearValue: 0.0, _expected: backgroundColor },
+{ depthCompare: 'less-equal', depthClearValue: 1.0, _expected: triangleColor },
+{
+ depthCompare: 'less-equal',
+ depthClearValue: kMiddleDepthValue,
+ _expected: triangleColor
+},
+{ depthCompare: 'less-equal', depthClearValue: 0.0, _expected: backgroundColor },
+{ depthCompare: 'equal', depthClearValue: 1.0, _expected: backgroundColor },
+{ depthCompare: 'equal', depthClearValue: kMiddleDepthValue, _expected: triangleColor },
+{ depthCompare: 'equal', depthClearValue: 0.0, _expected: backgroundColor },
+{ depthCompare: 'not-equal', depthClearValue: 1.0, _expected: triangleColor },
+{
+ depthCompare: 'not-equal',
+ depthClearValue: kMiddleDepthValue,
+ _expected: backgroundColor
+},
+{ depthCompare: 'not-equal', depthClearValue: 0.0, _expected: triangleColor },
+{ depthCompare: 'greater-equal', depthClearValue: 1.0, _expected: backgroundColor },
+{
+ depthCompare: 'greater-equal',
+ depthClearValue: kMiddleDepthValue,
+ _expected: triangleColor
+},
+{ depthCompare: 'greater-equal', depthClearValue: 0.0, _expected: triangleColor },
+{ depthCompare: 'greater', depthClearValue: 1.0, _expected: backgroundColor },
+{ depthCompare: 'greater', depthClearValue: kMiddleDepthValue, _expected: backgroundColor },
+{ depthCompare: 'greater', depthClearValue: 0.0, _expected: triangleColor },
+{ depthCompare: 'always', depthClearValue: 1.0, _expected: triangleColor },
+{ depthCompare: 'always', depthClearValue: kMiddleDepthValue, _expected: triangleColor },
+{ depthCompare: 'always', depthClearValue: 0.0, _expected: triangleColor }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { depthCompare, depthClearValue, _expected, format } = t.params;
+
+ const colorAttachmentFormat = 'rgba8unorm';
+ const colorAttachment = t.device.createTexture({
+ format: colorAttachmentFormat,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const depthTexture = t.device.createTexture({
+ size: { width: 1, height: 1 },
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
+ });
+ const depthTextureView = depthTexture.createView();
+
+ const pipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.5, 0.5, ${kMiddleDepthValue}, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 1.0, 1.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorAttachmentFormat }]
+ },
+ primitive: { topology: 'point-list' },
+ depthStencil: {
+ depthWriteEnabled: true,
+ depthCompare,
+ format
+ }
+ };
+ const pipeline = t.device.createRenderPipeline(pipelineDescriptor);
+
+ const encoder = t.device.createCommandEncoder();
+ const depthStencilAttachment = {
+ view: depthTextureView,
+ depthClearValue,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store'
+ };
+ if (kTextureFormatInfo[format].stencil) {
+ depthStencilAttachment.stencilClearValue = 0;
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ }
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ storeOp: 'store',
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear'
+ }],
+
+ depthStencilAttachment
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
+ {
+ coord: { x: 0, y: 0 },
+ exp: new Uint8Array(_expected)
+ }]
+ );
+});
+
+g.test('reverse_depth').
+desc(
+ `Tests simple rendering with reversed depth buffer, ensures depth test works properly: fragments are in correct order and out of range fragments are clipped.
+ Note that in real use case the depth range remapping is done by the modified projection matrix.
+(see https://developer.nvidia.com/content/depth-precision-visualized).`
+).
+params((u) => u.combine('reversed', [false, true])).
+fn((t) => {
+ const colorAttachmentFormat = 'rgba8unorm';
+ const colorAttachment = t.device.createTexture({
+ format: colorAttachmentFormat,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const depthBufferFormat = 'depth32float';
+ const depthTexture = t.device.createTexture({
+ size: { width: 1, height: 1 },
+ format: depthBufferFormat,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
+ });
+ const depthTextureView = depthTexture.createView();
+
+ const pipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ struct Output {
+ @builtin(position) Position : vec4<f32>,
+ @location(0) color : vec4<f32>,
+ };
+
+ @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;
+
+ var output : Output;
+ output.Position = vec4<f32>(0.5, 0.5, z, 1.0);
+ var colors : array<vec4<f32>, 4> = array<vec4<f32>, 4>(
+ vec4<f32>(1.0, 0.0, 0.0, 1.0),
+ vec4<f32>(0.0, 1.0, 0.0, 1.0),
+ vec4<f32>(0.0, 0.0, 1.0, 1.0),
+ vec4<f32>(1.0, 1.0, 1.0, 1.0)
+ );
+ output.color = colors[InstanceIndex];
+ return output;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main(
+ @location(0) color : vec4<f32>
+ ) -> @location(0) vec4<f32> {
+ return color;
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorAttachmentFormat }]
+ },
+ primitive: { topology: 'point-list' },
+ depthStencil: {
+ depthWriteEnabled: true,
+ depthCompare: t.params.reversed ? 'greater' : 'less',
+ format: depthBufferFormat
+ }
+ };
+ const pipeline = t.device.createRenderPipeline(pipelineDescriptor);
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ storeOp: 'store',
+ clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
+ loadOp: 'clear'
+ }],
+
+ depthStencilAttachment: {
+ view: depthTextureView,
+
+ depthClearValue: t.params.reversed ? 0.0 : 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store'
+ }
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(1, 4);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
+ {
+ coord: { x: 0, y: 0 },
+ exp: new Uint8Array(
+ t.params.reversed ? [0x00, 0xff, 0x00, 0xff] : [0xff, 0x00, 0x00, 0xff]
+ )
+ }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_bias.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_bias.spec.js
new file mode 100644
index 0000000000..4cae7f53eb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_bias.spec.js
@@ -0,0 +1,352 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests render results with different depth bias values like 'positive', 'negative',
+'slope', 'clamp', etc.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { unreachable } from '../../../../common/util/util.js';
+import {
+ kTextureFormatInfo } from
+
+
+'../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { TexelView } from '../../../util/texture/texel_view.js';var
+
+QuadAngle = /*#__PURE__*/function (QuadAngle) {QuadAngle[QuadAngle["Flat"] = 0] = "Flat";QuadAngle[QuadAngle["TiltedX"] = 1] = "TiltedX";return QuadAngle;}(QuadAngle || {});
+
+
+
+
+// Floating point depth buffers use the following formula to calculate bias
+// bias = depthBias * 2 ** (exponent(max z of primitive) - number of bits in mantissa) +
+// slopeScale * maxSlope
+// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage-depth-bias
+// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdSetDepthBias.html
+// https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516269-setdepthbias
+//
+// To get a final bias of 0.25 for primitives with z = 0.25, we can use
+// depthBias = 0.25 / (2 ** (-2 - 23)) = 8388608.
+const kPointTwoFiveBiasForPointTwoFiveZOnFloat = 8388608;
+
+class DepthBiasTest extends TextureTestMixin(GPUTest) {
+ runDepthBiasTestInternal(
+ depthFormat,
+ {
+ quadAngle,
+ bias,
+ biasSlopeScale,
+ biasClamp,
+ initialDepth
+
+
+
+
+
+
+ })
+ {
+ const renderTargetFormat = 'rgba8unorm';
+ const depthFormatInfo = kTextureFormatInfo[depthFormat];
+
+ let vertexShaderCode;
+ switch (quadAngle) {
+ case QuadAngle.Flat:
+ // Draw a square at z = 0.25.
+ vertexShaderCode = `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ 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));
+ return vec4<f32>(pos[VertexIndex], 0.25, 1.0);
+ }
+ `;
+ break;
+ case QuadAngle.TiltedX:
+ // Draw a square ranging from 0 to 0.5, bottom to top.
+ vertexShaderCode = `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ var pos = array<vec3<f32>, 6>(
+ vec3<f32>(-1.0, -1.0, 0.0),
+ vec3<f32>( 1.0, -1.0, 0.0),
+ vec3<f32>(-1.0, 1.0, 0.5),
+ vec3<f32>(-1.0, 1.0, 0.5),
+ vec3<f32>( 1.0, -1.0, 0.0),
+ vec3<f32>( 1.0, 1.0, 0.5));
+ return vec4<f32>(pos[VertexIndex], 1.0);
+ }
+ `;
+ break;
+ default:
+ unreachable();
+ }
+
+ const renderTarget = this.trackForCleanup(
+ this.device.createTexture({
+ format: renderTargetFormat,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const depthTexture = this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: depthFormat,
+ sampleCount: 1,
+ mipLevelCount: 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ })
+ );
+
+ const depthStencilAttachment = {
+ view: depthTexture.createView(),
+ depthLoadOp: depthFormatInfo.depth ? 'clear' : undefined,
+ depthStoreOp: depthFormatInfo.depth ? 'store' : undefined,
+ stencilLoadOp: depthFormatInfo.stencil ? 'clear' : undefined,
+ stencilStoreOp: depthFormatInfo.stencil ? 'store' : undefined,
+ depthClearValue: initialDepth
+ };
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ loadOp: 'load'
+ }],
+
+ depthStencilAttachment
+ });
+
+ let depthCompare = 'always';
+ if (depthFormat !== 'depth32float') {
+ depthCompare = 'greater';
+ }
+
+ const testState = {
+ format: depthFormat,
+ depthCompare,
+ depthWriteEnabled: true,
+ depthBias: bias,
+ depthBiasSlopeScale: biasSlopeScale,
+ depthBiasClamp: biasClamp
+ };
+
+ // Draw a square with the given depth state and bias values.
+ const testPipeline = this.createRenderPipelineForTest(vertexShaderCode, testState);
+ pass.setPipeline(testPipeline);
+ pass.draw(6);
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ return { renderTarget, depthTexture };
+ }
+
+ runDepthBiasTest(
+ depthFormat,
+ {
+ quadAngle,
+ bias,
+ biasSlopeScale,
+ biasClamp,
+ _expectedDepth
+
+
+
+
+
+
+ })
+ {
+ const { depthTexture } = this.runDepthBiasTestInternal(depthFormat, {
+ quadAngle,
+ bias,
+ biasSlopeScale,
+ biasClamp,
+ initialDepth: 0
+ });
+
+ const expColor = { Depth: _expectedDepth };
+ const expTexelView = TexelView.fromTexelsAsColors(depthFormat, (_coords) => expColor);
+ this.expectTexelViewComparisonIsOkInTexture({ texture: depthTexture }, expTexelView, [1, 1]);
+ }
+
+ runDepthBiasTestFor24BitFormat(
+ depthFormat,
+ {
+ quadAngle,
+ bias,
+ biasSlopeScale,
+ biasClamp,
+ _expectedColor
+
+
+
+
+
+
+ })
+ {
+ const { renderTarget } = this.runDepthBiasTestInternal(depthFormat, {
+ quadAngle,
+ bias,
+ biasSlopeScale,
+ biasClamp,
+ initialDepth: 0.4
+ });
+
+ const renderTargetFormat = 'rgba8unorm';
+ const expColor = {
+ R: _expectedColor[0],
+ G: _expectedColor[1],
+ B: _expectedColor[2],
+ A: _expectedColor[3]
+ };
+ const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, (_coords) => expColor);
+ this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
+ }
+
+ createRenderPipelineForTest(
+ vertex,
+ depthStencil)
+ {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: vertex
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ targets: [{ format: 'rgba8unorm' }],
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ depthStencil
+ });
+ }
+}
+
+export const g = makeTestGroup(DepthBiasTest);
+
+g.test('depth_bias').
+desc(
+ `
+ Tests that a square with different depth bias values like 'positive', 'negative',
+ 'slope', 'clamp', etc. is drawn as expected.
+ `
+).
+params((u) =>
+u //
+.combineWithParams([
+{
+ quadAngle: QuadAngle.Flat,
+ bias: kPointTwoFiveBiasForPointTwoFiveZOnFloat,
+ biasSlopeScale: 0,
+ biasClamp: 0,
+ _expectedDepth: 0.5
+},
+{
+ quadAngle: QuadAngle.Flat,
+ bias: kPointTwoFiveBiasForPointTwoFiveZOnFloat,
+ biasSlopeScale: 0,
+ biasClamp: 0.125,
+ _expectedDepth: 0.375
+},
+{
+ quadAngle: QuadAngle.Flat,
+ bias: -kPointTwoFiveBiasForPointTwoFiveZOnFloat,
+ biasSlopeScale: 0,
+ biasClamp: 0.125,
+ _expectedDepth: 0
+},
+{
+ quadAngle: QuadAngle.Flat,
+ bias: -kPointTwoFiveBiasForPointTwoFiveZOnFloat,
+ biasSlopeScale: 0,
+ biasClamp: -0.125,
+ _expectedDepth: 0.125
+},
+{
+ quadAngle: QuadAngle.TiltedX,
+ bias: 0,
+ biasSlopeScale: 0,
+ biasClamp: 0,
+ _expectedDepth: 0.25
+},
+{
+ quadAngle: QuadAngle.TiltedX,
+ bias: 0,
+ biasSlopeScale: 1,
+ biasClamp: 0,
+ _expectedDepth: 0.75
+},
+{
+ quadAngle: QuadAngle.TiltedX,
+ bias: 0,
+ biasSlopeScale: -0.5,
+ biasClamp: 0,
+ _expectedDepth: 0
+}]
+)
+).
+fn((t) => {
+ t.runDepthBiasTest('depth32float', t.params);
+});
+
+g.test('depth_bias_24bit_format').
+desc(
+ `
+ Tests that a square with different depth bias values like 'positive', 'negative',
+ 'slope', 'clamp', etc. is drawn as expected with 24 bit depth format.
+
+ TODO: Enhance these tests by reading back the depth (emulating the copy using texture sampling)
+ and checking the result directly, like the non-24-bit depth tests, instead of just relying on
+ whether the depth test passes or fails.
+ `
+).
+params((u) =>
+u //
+.combine('format', ['depth24plus', 'depth24plus-stencil8']).
+combineWithParams([
+{
+ quadAngle: QuadAngle.Flat,
+ bias: 0.25 * (1 << 25),
+ biasSlopeScale: 0,
+ biasClamp: 0,
+ _expectedColor: new Float32Array([1.0, 0.0, 0.0, 1.0])
+},
+{
+ quadAngle: QuadAngle.TiltedX,
+ bias: 0.25 * (1 << 25),
+ biasSlopeScale: 1,
+ biasClamp: 0,
+ _expectedColor: new Float32Array([1.0, 0.0, 0.0, 1.0])
+},
+{
+ quadAngle: QuadAngle.Flat,
+ bias: 0.25 * (1 << 25),
+ biasSlopeScale: 0,
+ biasClamp: 0.1,
+ _expectedColor: new Float32Array([0.0, 0.0, 0.0, 0.0])
+}]
+)
+).
+fn((t) => {
+ const { format } = t.params;
+ t.runDepthBiasTestFor24BitFormat(format, t.params);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_clip_clamp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_clip_clamp.spec.js
new file mode 100644
index 0000000000..28d639990d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/depth_clip_clamp.spec.js
@@ -0,0 +1,524 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for depth clipping, depth clamping (at various points in the pipeline), and maybe extended
+depth ranges as well.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import {
+ checkElementsBetween,
+ checkElementsPassPredicate } from
+
+'../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('depth_clamp_and_clip').
+desc(
+ `
+Depth written to the depth attachment should always be in the range of the viewport depth,
+even if it was written by the fragment shader (using frag_depth). If depth clipping is enabled,
+primitives should be clipped to the viewport depth before rasterization; if not, these fragments
+should be rasterized, and the fragment shader should receive out-of-viewport position.z values.
+
+To test this, render NxN points, with N vertex depth values, by (if writeDepth=true) N
+frag_depth values with the viewport depth set to [0.25,0.75].
+
+While rendering, check the fragment input position.z has the expected value (for all fragments that
+were produced by the rasterizer) by writing the diff to a storage buffer, which is later checked to
+be all (near) 0.
+
+Then, run another pass (which outputs every point at z=0.5 to avoid clipping) to verify the depth
+buffer contents by outputting the expected depth with depthCompare:'not-equal': any fragments that
+have unexpected values then get drawn to the color buffer, which is later checked to be empty.`
+).
+params((u) =>
+u //
+.combine('format', kDepthStencilFormats).
+filter((p) => !!kTextureFormatInfo[p.format].depth).
+combine('unclippedDepth', [undefined, false, true]).
+combine('writeDepth', [false, true]).
+combine('multisampled', [false, true])
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+
+ t.selectDeviceOrSkipTestCase([
+ t.params.unclippedDepth ? 'depth-clip-control' : undefined,
+ info.feature]
+ );
+}).
+fn(async (t) => {
+ const { format, unclippedDepth, writeDepth, multisampled } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ /** Number of depth values to test for both vertex output and frag_depth output. */
+ const kNumDepthValues = 8;
+ /** Test every combination of vertex output and frag_depth output. */
+ const kNumTestPoints = kNumDepthValues * kNumDepthValues;
+ const kViewportMinDepth = 0.25;
+ const kViewportMaxDepth = 0.75;
+
+ const shaderSource = `
+ // Test depths, with viewport range corresponding to [0,1].
+ var<private> kDepths: array<f32, ${kNumDepthValues}> = array<f32, ${kNumDepthValues}>(
+ -1.0, -0.5, 0.0, 0.25, 0.75, 1.0, 1.5, 2.0);
+
+ const vpMin: f32 = ${kViewportMinDepth};
+ const vpMax: f32 = ${kViewportMaxDepth};
+
+ // Draw the points in a straight horizontal row, one per pixel.
+ fn vertexX(idx: u32) -> f32 {
+ return (f32(idx) + 0.5) * 2.0 / ${kNumTestPoints}.0 - 1.0;
+ }
+
+ // Test vertex shader's position.z output.
+ // Here, the viewport range corresponds to position.z in [0,1].
+ fn vertexZ(idx: u32) -> f32 {
+ return kDepths[idx / ${kNumDepthValues}u];
+ }
+
+ // Test fragment shader's expected position.z input.
+ // Here, the viewport range corresponds to position.z in [vpMin,vpMax], but
+ // unclipped values extend beyond that range.
+ fn expectedFragPosZ(idx: u32) -> f32 {
+ return vpMin + vertexZ(idx) * (vpMax - vpMin);
+ }
+
+ //////// "Test" entry points
+
+ struct VFTest {
+ @builtin(position) pos: vec4<f32>,
+ @location(0) @interpolate(flat) vertexIndex: u32,
+ };
+
+ @vertex
+ fn vtest(@builtin(vertex_index) idx: u32) -> VFTest {
+ var vf: VFTest;
+ vf.pos = vec4<f32>(vertexX(idx), 0.0, vertexZ(idx), 1.0);
+ vf.vertexIndex = idx;
+ return vf;
+ }
+
+ struct Output {
+ // Each fragment (that didn't get clipped) writes into one element of this output.
+ // (Anything that doesn't get written is already zero.)
+ fragInputZDiff: array<f32, ${kNumTestPoints}>
+ };
+ @group(0) @binding(0) var <storage, read_write> output: Output;
+
+ fn checkZ(vf: VFTest) {
+ output.fragInputZDiff[vf.vertexIndex] = vf.pos.z - expectedFragPosZ(vf.vertexIndex);
+ }
+
+ @fragment
+ fn ftest_WriteDepth(vf: VFTest) -> @builtin(frag_depth) f32 {
+ checkZ(vf);
+ return kDepths[vf.vertexIndex % ${kNumDepthValues}u];
+ }
+
+ @fragment
+ fn ftest_NoWriteDepth(vf: VFTest) {
+ checkZ(vf);
+ }
+
+ //////// "Check" entry points
+
+ struct VFCheck {
+ @builtin(position) pos: vec4<f32>,
+ @location(0) @interpolate(flat) vertexIndex: u32,
+ };
+
+ @vertex
+ fn vcheck(@builtin(vertex_index) idx: u32) -> VFCheck {
+ var vf: VFCheck;
+ // Depth=0.5 because we want to render every point, not get clipped.
+ vf.pos = vec4<f32>(vertexX(idx), 0.0, 0.5, 1.0);
+ vf.vertexIndex = idx;
+ return vf;
+ }
+
+ struct FCheck {
+ @builtin(frag_depth) depth: f32,
+ @location(0) color: f32,
+ };
+
+ @fragment
+ fn fcheck(vf: VFCheck) -> FCheck {
+ let vertZ = vertexZ(vf.vertexIndex);
+ let outOfRange = vertZ < 0.0 || vertZ > 1.0;
+ let expFragPosZ = expectedFragPosZ(vf.vertexIndex);
+
+ let writtenDepth = kDepths[vf.vertexIndex % ${kNumDepthValues}u];
+
+ let expectedDepthWriteInput = ${writeDepth ? 'writtenDepth' : 'expFragPosZ'};
+ var expectedDepthBufferValue = clamp(expectedDepthWriteInput, vpMin, vpMax);
+ if (${!unclippedDepth} && outOfRange) {
+ // Test fragment should have been clipped; expect the depth attachment to
+ // have its clear value (0.5).
+ expectedDepthBufferValue = 0.5;
+ }
+
+ var f: FCheck;
+ f.depth = expectedDepthBufferValue;
+ f.color = 1.0; // Color written if the resulting depth is unexpected.
+ return f;
+ }
+ `;
+ const module = t.device.createShaderModule({ code: shaderSource });
+
+ // Draw points at different vertex depths and fragment depths into the depth attachment,
+ // with a viewport of [0.25,0.75].
+ const testPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vtest' },
+ primitive: {
+ topology: 'point-list',
+ unclippedDepth
+ },
+ depthStencil: { format, depthWriteEnabled: true, depthCompare: 'always' },
+ multisample: multisampled ? { count: 4 } : undefined,
+ fragment: {
+ module,
+ entryPoint: writeDepth ? 'ftest_WriteDepth' : 'ftest_NoWriteDepth',
+ targets: []
+ }
+ });
+
+ // Use depth comparison to check that the depth attachment now has the expected values.
+ const checkPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vcheck' },
+ primitive: { topology: 'point-list' },
+ depthStencil: {
+ format,
+ // NOTE: This check is probably very susceptible to floating point error. If it fails, maybe
+ // replace it with two checks (less + greater) with an epsilon applied in the check shader?
+ depthCompare: 'not-equal', // Expect every depth value to be exactly equal.
+ depthWriteEnabled: true // If the check failed, overwrite with the expected result.
+ },
+ multisample: multisampled ? { count: 4 } : undefined,
+ fragment: { module, entryPoint: 'fcheck', targets: [{ format: 'r8unorm' }] }
+ });
+
+ const dsTexture = t.device.createTexture({
+ format,
+ size: [kNumTestPoints],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ sampleCount: multisampled ? 4 : 1
+ });
+ const dsTextureView = dsTexture.createView();
+
+ const checkTextureDesc = {
+ format: 'r8unorm',
+ size: [kNumTestPoints],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ };
+ const checkTexture = t.device.createTexture(checkTextureDesc);
+ const checkTextureView = checkTexture.createView();
+ const checkTextureMSView = multisampled ?
+ t.device.createTexture({ ...checkTextureDesc, sampleCount: 4 }).createView() :
+ undefined;
+
+ const dsActual =
+ !multisampled && info.bytesPerBlock ?
+ t.device.createBuffer({
+ size: kNumTestPoints * info.bytesPerBlock,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
+ }) :
+ undefined;
+ const dsExpected =
+ !multisampled && info.bytesPerBlock ?
+ t.device.createBuffer({
+ size: kNumTestPoints * info.bytesPerBlock,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
+ }) :
+ undefined;
+ const checkBuffer = t.device.createBuffer({
+ size: kNumTestPoints,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
+ });
+
+ const fragInputZFailedBuffer = t.device.createBuffer({
+ size: 4 * kNumTestPoints,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ const testBindGroup = t.device.createBindGroup({
+ layout: testPipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: fragInputZFailedBuffer } }]
+ });
+
+ const enc = t.device.createCommandEncoder();
+ {
+ const pass = enc.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: dsTextureView,
+ depthClearValue: 0.5, // Will see this depth value if the fragment was clipped.
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilClearValue: info.stencil ? 0 : undefined,
+ stencilLoadOp: info.stencil ? 'clear' : undefined,
+ stencilStoreOp: info.stencil ? 'discard' : undefined
+ }
+ });
+ pass.setPipeline(testPipeline);
+ pass.setBindGroup(0, testBindGroup);
+ pass.setViewport(0, 0, kNumTestPoints, 1, kViewportMinDepth, kViewportMaxDepth);
+ pass.draw(kNumTestPoints);
+ pass.end();
+ }
+ if (dsActual) {
+ enc.copyTextureToBuffer({ texture: dsTexture }, { buffer: dsActual }, [kNumTestPoints]);
+ }
+ {
+ const clearValue = [0, 0, 0, 0]; // Will see this color if the check passed.
+ const pass = enc.beginRenderPass({
+ colorAttachments: [
+ checkTextureMSView ?
+ {
+ view: checkTextureMSView,
+ resolveTarget: checkTextureView,
+ clearValue,
+ loadOp: 'clear',
+ storeOp: 'discard'
+ } :
+ { view: checkTextureView, clearValue, loadOp: 'clear', storeOp: 'store' }],
+
+ depthStencilAttachment: {
+ view: dsTextureView,
+ depthLoadOp: 'load',
+ depthStoreOp: 'store',
+ stencilClearValue: info.stencil ? 0 : undefined,
+ stencilLoadOp: info.stencil ? 'clear' : undefined,
+ stencilStoreOp: info.stencil ? 'discard' : undefined
+ }
+ });
+ pass.setPipeline(checkPipeline);
+ pass.setViewport(0, 0, kNumTestPoints, 1, 0.0, 1.0);
+ pass.draw(kNumTestPoints);
+ pass.end();
+ }
+ enc.copyTextureToBuffer({ texture: checkTexture }, { buffer: checkBuffer }, [kNumTestPoints]);
+ if (dsExpected) {
+ enc.copyTextureToBuffer({ texture: dsTexture }, { buffer: dsExpected }, [kNumTestPoints]);
+ }
+ t.device.queue.submit([enc.finish()]);
+
+ t.expectGPUBufferValuesPassCheck(
+ fragInputZFailedBuffer,
+ (a) => checkElementsBetween(a, [() => -1e-5, () => 1e-5]),
+ { type: Float32Array, typedLength: kNumTestPoints }
+ );
+
+ const kCheckPassedValue = 0;
+ const predicatePrinter = [
+ { leftHeader: 'expected ==', getValueForCell: (_index) => kCheckPassedValue }];
+
+ if (dsActual && dsExpected && format === 'depth32float') {
+ await Promise.all([dsActual.mapAsync(GPUMapMode.READ), dsExpected.mapAsync(GPUMapMode.READ)]);
+ const act = new Float32Array(dsActual.getMappedRange());
+ const exp = new Float32Array(dsExpected.getMappedRange());
+ predicatePrinter.push(
+ { leftHeader: 'act ==', getValueForCell: (index) => act[index].toFixed(2) },
+ { leftHeader: 'exp ==', getValueForCell: (index) => exp[index].toFixed(2) }
+ );
+ }
+ t.expectGPUBufferValuesPassCheck(
+ checkBuffer,
+ (a) =>
+ checkElementsPassPredicate(a, (_index, value) => value === kCheckPassedValue, {
+ predicatePrinter
+ }),
+ { type: Uint8Array, typedLength: kNumTestPoints, method: 'map' }
+ );
+});
+
+g.test('depth_test_input_clamped').
+desc(
+ `
+Input to the depth test should always be in the range of viewport depth, even if it was written by
+the fragment shader (using frag_depth).
+
+To test this, first initialize the depth buffer with N expected values (by writing frag_depth, with
+the default viewport). These expected values are clamped by the shader to [0.25, 0.75].
+
+Then, run another pass with the viewport depth set to [0.25,0.75], and output various (unclamped)
+frag_depth values from its fragment shader with depthCompare:'not-equal'. These should get clamped;
+any fragments that have unexpected values then get drawn to the color buffer, which is later checked
+to be empty.`
+).
+params((u) =>
+u //
+.combine('format', kDepthStencilFormats).
+filter((p) => !!kTextureFormatInfo[p.format].depth).
+combine('unclippedDepth', [false, true]).
+combine('multisampled', [false, true])
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+
+ t.selectDeviceOrSkipTestCase([
+ t.params.unclippedDepth ? 'depth-clip-control' : undefined,
+ info.feature]
+ );
+}).
+fn((t) => {
+ const { format, unclippedDepth, multisampled } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const kNumDepthValues = 8;
+ const kViewportMinDepth = 0.25;
+ const kViewportMaxDepth = 0.75;
+
+ const shaderSource = `
+ // Test depths, with viewport range corresponding to [0,1].
+ var<private> kDepths: array<f32, ${kNumDepthValues}> = array<f32, ${kNumDepthValues}>(
+ -1.0, -0.5, 0.0, 0.25, 0.75, 1.0, 1.5, 2.0);
+
+ const vpMin: f32 = ${kViewportMinDepth};
+ const vpMax: f32 = ${kViewportMaxDepth};
+
+ // Draw the points in a straight horizontal row, one per pixel.
+ fn vertexX(idx: u32) -> f32 {
+ return (f32(idx) + 0.5) * 2.0 / ${kNumDepthValues}.0 - 1.0;
+ }
+
+ struct VF {
+ @builtin(position) pos: vec4<f32>,
+ @location(0) @interpolate(flat) vertexIndex: u32,
+ };
+
+ @vertex
+ fn vmain(@builtin(vertex_index) idx: u32) -> VF {
+ var vf: VF;
+ // Depth=0.5 because we want to render every point, not get clipped.
+ vf.pos = vec4<f32>(vertexX(idx), 0.0, 0.5, 1.0);
+ vf.vertexIndex = idx;
+ return vf;
+ }
+
+ @fragment
+ fn finit(vf: VF) -> @builtin(frag_depth) f32 {
+ // Expected values of the ftest pipeline.
+ return clamp(kDepths[vf.vertexIndex], vpMin, vpMax);
+ }
+
+ struct FTest {
+ @builtin(frag_depth) depth: f32,
+ @location(0) color: f32,
+ };
+
+ @fragment
+ fn ftest(vf: VF) -> FTest {
+ var f: FTest;
+ f.depth = kDepths[vf.vertexIndex]; // Should get clamped to the viewport.
+ f.color = 1.0; // Color written if the resulting depth is unexpected.
+ return f;
+ }
+ `;
+
+ const module = t.device.createShaderModule({ code: shaderSource });
+
+ // Initialize depth attachment with expected values, in [0.25,0.75].
+ const initPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vmain' },
+ primitive: { topology: 'point-list' },
+ depthStencil: { format, depthWriteEnabled: true, depthCompare: 'always' },
+ multisample: multisampled ? { count: 4 } : undefined,
+ fragment: { module, entryPoint: 'finit', targets: [] }
+ });
+
+ // With a viewport set to [0.25,0.75], output values in [0.0,1.0] and check they're clamped
+ // before the depth test, regardless of whether unclippedDepth is enabled.
+ const testPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vmain' },
+ primitive: {
+ topology: 'point-list',
+ unclippedDepth
+ },
+ depthStencil: { format, depthCompare: 'not-equal', depthWriteEnabled: false },
+ multisample: multisampled ? { count: 4 } : undefined,
+ fragment: { module, entryPoint: 'ftest', targets: [{ format: 'r8unorm' }] }
+ });
+
+ const dsTexture = t.device.createTexture({
+ format,
+ size: [kNumDepthValues],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ sampleCount: multisampled ? 4 : 1
+ });
+ const dsTextureView = dsTexture.createView();
+
+ const testTextureDesc = {
+ format: 'r8unorm',
+ size: [kNumDepthValues],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ };
+ const testTexture = t.device.createTexture(testTextureDesc);
+ const testTextureView = testTexture.createView();
+ const testTextureMSView = multisampled ?
+ t.device.createTexture({ ...testTextureDesc, sampleCount: 4 }).createView() :
+ undefined;
+
+ const resultBuffer = t.device.createBuffer({
+ size: kNumDepthValues,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
+ });
+
+ const enc = t.device.createCommandEncoder();
+ {
+ const pass = enc.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: dsTextureView,
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilClearValue: info.stencil ? 0 : undefined,
+ stencilLoadOp: info.stencil ? 'clear' : undefined,
+ stencilStoreOp: info.stencil ? 'discard' : undefined
+ }
+ });
+ pass.setPipeline(initPipeline);
+ pass.draw(kNumDepthValues);
+ pass.end();
+ }
+ {
+ const clearValue = [0, 0, 0, 0]; // Will see this color if the test passed.
+ const pass = enc.beginRenderPass({
+ colorAttachments: [
+ testTextureMSView ?
+ {
+ view: testTextureMSView,
+ resolveTarget: testTextureView,
+ clearValue,
+ loadOp: 'clear',
+ storeOp: 'discard'
+ } :
+ { view: testTextureView, clearValue, loadOp: 'clear', storeOp: 'store' }],
+
+ depthStencilAttachment: {
+ view: dsTextureView,
+ depthLoadOp: 'load',
+ depthStoreOp: 'store',
+ stencilClearValue: info.stencil ? 0 : undefined,
+ stencilLoadOp: info.stencil ? 'clear' : undefined,
+ stencilStoreOp: info.stencil ? 'discard' : undefined
+ }
+ });
+ pass.setPipeline(testPipeline);
+ pass.setViewport(0, 0, kNumDepthValues, 1, kViewportMinDepth, kViewportMaxDepth);
+ pass.draw(kNumDepthValues);
+ pass.end();
+ }
+ enc.copyTextureToBuffer({ texture: testTexture }, { buffer: resultBuffer }, [kNumDepthValues]);
+ t.device.queue.submit([enc.finish()]);
+
+ t.expectGPUBufferValuesEqual(resultBuffer, new Uint8Array(kNumDepthValues), 0, {
+ method: 'map'
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/draw.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/draw.spec.js
new file mode 100644
index 0000000000..541b876485
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/draw.spec.js
@@ -0,0 +1,768 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for the general aspects of draw/drawIndexed/drawIndirect/drawIndexedIndirect.
+
+Primitive topology tested in api/operation/render_pipeline/primitive_topology.spec.ts.
+Index format tested in api/operation/command_buffer/render/state_tracking.spec.ts.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ assert } from
+
+
+'../../../../common/util/util.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+
+
+class DrawTest extends TextureTestMixin(GPUTest) {
+ checkTriangleDraw(opts)
+
+
+
+
+
+
+
+
+
+ {
+ // Set fallbacks when parameters are undefined in order to calculate the expected values.
+ const defaulted = {
+ firstIndex: opts.firstIndex ?? 0,
+ count: opts.count,
+ firstInstance: opts.firstInstance ?? 0,
+ instanceCount: opts.instanceCount ?? 1,
+ indexed: opts.indexed,
+ indirect: opts.indirect,
+ vertexBufferOffset: opts.vertexBufferOffset,
+ indexBufferOffset: opts.indexBufferOffset ?? 0,
+ baseVertex: opts.baseVertex ?? 0
+ };
+
+ const renderTargetSize = [72, 36];
+
+ // The test will split up the render target into a grid where triangles of
+ // increasing primitive id will be placed along the X axis, and triangles
+ // of increasing instance id will be placed along the Y axis. The size of the
+ // grid is based on the max primitive id and instance id used.
+ const numX = 6;
+ const numY = 6;
+ const tileSizeX = renderTargetSize[0] / numX;
+ const tileSizeY = renderTargetSize[1] / numY;
+
+ // |\
+ // | \
+ // |______\
+ // Unit triangle shaped like this. 0-1 Y-down.
+
+ const triangleVertices = [
+ 0.0, 0.0,
+ 0.0, 1.0,
+ 1.0, 1.0];
+
+
+ const renderTarget = this.device.createTexture({
+ size: renderTargetSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ format: 'rgba8unorm'
+ });
+
+ const vertexModule = this.device.createShaderModule({
+ code: `
+struct Inputs {
+ @builtin(vertex_index) vertex_index : u32,
+ @builtin(instance_index) instance_id : u32,
+ @location(0) vertexPosition : vec2<f32>,
+};
+
+@vertex fn vert_main(input : Inputs
+ ) -> @builtin(position) vec4<f32> {
+ // 3u is the number of points in a triangle to convert from index
+ // to id.
+ var vertex_id : u32 = input.vertex_index / 3u;
+
+ var x : f32 = (input.vertexPosition.x + f32(vertex_id)) / ${numX}.0;
+ var y : f32 = (input.vertexPosition.y + f32(input.instance_id)) / ${numY}.0;
+
+ // (0,1) y-down space to (-1,1) y-up NDC
+ x = 2.0 * x - 1.0;
+ y = -2.0 * y + 1.0;
+ return vec4<f32>(x, y, 0.0, 1.0);
+}
+`
+ });
+
+ const fragmentModule = this.device.createShaderModule({
+ code: `
+struct Output {
+ value : u32
+};
+
+@group(0) @binding(0) var<storage, read_write> output : Output;
+
+@fragment fn frag_main() -> @location(0) vec4<f32> {
+ output.value = 1u;
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+}
+`
+ });
+
+ const pipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: vertexModule,
+ entryPoint: 'vert_main',
+ buffers: [
+ {
+ attributes: [
+ {
+ shaderLocation: 0,
+ format: 'float32x2',
+ offset: 0
+ }],
+
+ arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT
+ }]
+
+ },
+ fragment: {
+ module: fragmentModule,
+ entryPoint: 'frag_main',
+ targets: [
+ {
+ format: 'rgba8unorm'
+ }]
+
+ }
+ });
+
+ const resultBuffer = this.device.createBuffer({
+ size: Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+
+ const resultBindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: resultBuffer
+ }
+ }]
+
+ });
+
+ const commandEncoder = this.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(0, resultBindGroup);
+
+ if (defaulted.indexed) {
+ // INDEXED DRAW
+ assert(defaulted.baseVertex !== undefined);
+ assert(defaulted.indexBufferOffset !== undefined);
+
+ renderPass.setIndexBuffer(
+ this.makeBufferWithContents(
+ new Uint32Array([
+ // Offset the index buffer contents by empty data.
+ ...new Array(defaulted.indexBufferOffset / Uint32Array.BYTES_PER_ELEMENT),
+
+ 0, 1, 2, //
+ 3, 4, 5, //
+ 6, 7, 8 //
+ ]),
+ GPUBufferUsage.INDEX
+ ),
+ 'uint32',
+ defaulted.indexBufferOffset
+ );
+
+ renderPass.setVertexBuffer(
+ 0,
+ this.makeBufferWithContents(
+ new Float32Array([
+ // Offset the vertex buffer contents by empty data.
+ ...new Array(defaulted.vertexBufferOffset / Float32Array.BYTES_PER_ELEMENT),
+
+ // selected with base_vertex=0
+ // count=6
+ ...triangleVertices, // | count=6;first=3
+ ...triangleVertices, // | |
+ ...triangleVertices, // |
+
+ // selected with base_vertex=9
+ // count=6
+ ...triangleVertices, // | count=6;first=3
+ ...triangleVertices, // | |
+ ...triangleVertices // |
+ ]),
+ GPUBufferUsage.VERTEX
+ ),
+ defaulted.vertexBufferOffset
+ );
+
+ if (defaulted.indirect) {
+ const args = [
+ defaulted.count,
+ defaulted.instanceCount,
+ defaulted.firstIndex,
+ defaulted.baseVertex,
+ defaulted.firstInstance];
+
+ renderPass.drawIndexedIndirect(
+ this.makeBufferWithContents(new Uint32Array(args), GPUBufferUsage.INDIRECT),
+ 0
+ );
+ } else {
+ const args = [
+ opts.count,
+ opts.instanceCount,
+ opts.firstIndex,
+ opts.baseVertex,
+ opts.firstInstance];
+
+ renderPass.drawIndexed.apply(renderPass, [...args]);
+ }
+ } else {
+ // NON-INDEXED DRAW
+ renderPass.setVertexBuffer(
+ 0,
+ this.makeBufferWithContents(
+ new Float32Array([
+ // Offset the vertex buffer contents by empty data.
+ ...new Array(defaulted.vertexBufferOffset / Float32Array.BYTES_PER_ELEMENT),
+
+ // count=6
+ ...triangleVertices, // | count=6;first=3
+ ...triangleVertices, // | |
+ ...triangleVertices // |
+ ]),
+ GPUBufferUsage.VERTEX
+ ),
+ defaulted.vertexBufferOffset
+ );
+
+ if (defaulted.indirect) {
+ const args = [
+ defaulted.count,
+ defaulted.instanceCount,
+ defaulted.firstIndex,
+ defaulted.firstInstance];
+
+ renderPass.drawIndirect(
+ this.makeBufferWithContents(new Uint32Array(args), GPUBufferUsage.INDIRECT),
+ 0
+ );
+ } else {
+ const args = [opts.count, opts.instanceCount, opts.firstIndex, opts.firstInstance];
+ renderPass.draw.apply(renderPass, [...args]);
+ }
+ }
+
+ renderPass.end();
+ this.queue.submit([commandEncoder.finish()]);
+
+ const green = new Uint8Array([0, 255, 0, 255]);
+ const transparentBlack = new Uint8Array([0, 0, 0, 0]);
+
+ const didDraw = defaulted.count && defaulted.instanceCount;
+
+ this.expectGPUBufferValuesEqual(resultBuffer, new Uint32Array([didDraw ? 1 : 0]));
+
+ const baseVertexCount = defaulted.baseVertex ?? 0;
+ const pixelComparisons = [];
+ for (let primitiveId = 0; primitiveId < numX; ++primitiveId) {
+ for (let instanceId = 0; instanceId < numY; ++instanceId) {
+ let expectedColor = didDraw ? green : transparentBlack;
+ if (
+ primitiveId * 3 < defaulted.firstIndex + baseVertexCount ||
+ primitiveId * 3 >= defaulted.firstIndex + baseVertexCount + defaulted.count)
+ {
+ expectedColor = transparentBlack;
+ }
+
+ if (
+ instanceId < defaulted.firstInstance ||
+ instanceId >= defaulted.firstInstance + defaulted.instanceCount)
+ {
+ expectedColor = transparentBlack;
+ }
+
+ pixelComparisons.push({
+ coord: { x: (1 / 3 + primitiveId) * tileSizeX, y: (2 / 3 + instanceId) * tileSizeY },
+ exp: expectedColor
+ });
+ }
+ }
+ this.expectSinglePixelComparisonsAreOkInTexture({ texture: renderTarget }, pixelComparisons);
+ }
+}
+
+export const g = makeTestGroup(DrawTest);
+
+g.test('arguments').
+desc(
+ `Test that draw arguments are passed correctly by drawing triangles in a grid.
+Horizontally across the texture are triangles with increasing "primitive id".
+Vertically down the screen are triangles with increasing instance id.
+Increasing the |first| param should skip some of the beginning triangles on the horizontal axis.
+Increasing the |first_instance| param should skip of the beginning triangles on the vertical axis.
+The vertex buffer contains two sets of disjoint triangles, and base_vertex is used to select the second set.
+The test checks that the center of all of the expected triangles is drawn, and the others are empty.
+The fragment shader also writes out to a storage buffer. If the draw is zero-sized, check that no value is written.
+
+Params:
+ - first= {0, 3} - either the firstVertex or firstIndex
+ - count= {0, 3, 6} - either the vertexCount or indexCount
+ - first_instance= {0, 2}
+ - instance_count= {0, 1, 4}
+ - indexed= {true, false}
+ - indirect= {true, false}
+ - vertex_buffer_offset= {0, 32}
+ - index_buffer_offset= {0, 16} - only for indexed draws
+ - base_vertex= {0, 9} - only for indexed draws
+ `
+).
+params((u) =>
+u.
+combine('first', [0, 3]).
+combine('count', [0, 3, 6]).
+combine('first_instance', [0, 2]).
+combine('instance_count', [0, 1, 4]).
+combine('indexed', [false, true]).
+combine('indirect', [false, true]).
+combine('vertex_buffer_offset', [0, 32]).
+expand('index_buffer_offset', (p) => p.indexed ? [0, 16] : [undefined]).
+expand('base_vertex', (p) => p.indexed ? [0, 9] : [undefined])
+).
+beforeAllSubcases((t) => {
+ if (t.params.first_instance > 0 && t.params.indirect) {
+ t.selectDeviceOrSkipTestCase('indirect-first-instance');
+ }
+}).
+fn((t) => {
+ t.checkTriangleDraw({
+ firstIndex: t.params.first,
+ count: t.params.count,
+ firstInstance: t.params.first_instance,
+ instanceCount: t.params.instance_count,
+ indexed: t.params.indexed,
+ indirect: t.params.indirect,
+ vertexBufferOffset: t.params.vertex_buffer_offset,
+ indexBufferOffset: t.params.index_buffer_offset,
+ baseVertex: t.params.base_vertex
+ });
+});
+
+g.test('default_arguments').
+desc(
+ `
+ Test that defaults arguments are passed correctly by drawing triangles in a grid when they are not
+ defined. This test is written based on the 'arguments' with 'undefined' value in the parameters.
+ - mode= {draw, drawIndexed}
+ - arg= {instance_count, first_index, first_instance, base_vertex}
+ `
+).
+params((u) =>
+u.
+combine('mode', ['draw', 'drawIndexed']).
+beginSubcases().
+combine('instance_count', [undefined, 4]).
+combine('first_index', [undefined, 3]).
+combine('first_instance', [undefined, 2]).
+expand('base_vertex', (p) =>
+p.mode === 'drawIndexed' ? [undefined, 9] : [undefined]
+)
+).
+fn((t) => {
+ const kVertexCount = 3;
+ const kVertexBufferOffset = 32;
+ const kIndexBufferOffset = 16;
+
+ t.checkTriangleDraw({
+ firstIndex: t.params.first_index,
+ count: kVertexCount,
+ firstInstance: t.params.first_instance,
+ instanceCount: t.params.instance_count,
+ indexed: t.params.mode === 'drawIndexed',
+ indirect: false, // indirect
+ vertexBufferOffset: kVertexBufferOffset,
+ indexBufferOffset: kIndexBufferOffset,
+ baseVertex: t.params.base_vertex
+ });
+});
+
+g.test('vertex_attributes,basic').
+desc(
+ `Test basic fetching of vertex attributes.
+ Each vertex attribute is a single value and written out into a storage buffer.
+ Tests that vertices with offsets/strides for instanced/non-instanced attributes are
+ fetched correctly. Not all vertex formats are tested.
+
+ Params:
+ - vertex_attribute_count= {1, 4, 8, 16}
+ - vertex_buffer_count={1, 4, 8} - where # attributes is > 0
+ - vertex_format={uint32, float32}
+ - step_mode= {undefined, vertex, instance, mixed} - where mixed only applies for vertex_buffer_count > 1
+ `
+).
+params((u) =>
+u.
+combine('vertex_attribute_count', [1, 4, 8, 16]).
+combine('vertex_buffer_count', [1, 4, 8]).
+combine('vertex_format', ['uint32', 'float32']).
+combine('step_mode', [undefined, 'vertex', 'instance', 'mixed']).
+unless((p) => p.vertex_attribute_count < p.vertex_buffer_count).
+unless((p) => p.step_mode === 'mixed' && p.vertex_buffer_count <= 1)
+).
+fn((t) => {
+ const vertexCount = 4;
+ const instanceCount = 4;
+
+ // In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute.
+ const maxAttributes = t.device.limits.maxVertexAttributes - (t.isCompatibility ? 2 : 0);
+ const numAttributes = Math.min(maxAttributes, t.params.vertex_attribute_count);
+ const maxAttributesPerVertexBuffer = Math.ceil(numAttributes / t.params.vertex_buffer_count);
+
+ let shaderLocation = 0;
+ let attributeValue = 0;
+ const bufferLayouts = [];
+
+ let ExpectedDataConstructor;
+ switch (t.params.vertex_format) {
+ case 'uint32':
+ ExpectedDataConstructor = Uint32Array;
+ break;
+ case 'float32':
+ ExpectedDataConstructor = Float32Array;
+ break;
+ }
+
+ // Populate |bufferLayouts|, |vertexBufferData|, and |vertexBuffers|.
+ // We will use this to both create the render pipeline, and produce the
+ // expected data on the CPU.
+ // Attributes in each buffer will be interleaved.
+ const vertexBuffers = [];
+ const vertexBufferData = [];
+ for (let b = 0; b < t.params.vertex_buffer_count; ++b) {
+ const vertexBufferValues = [];
+
+ let offset = 0;
+ let stepMode = t.params.step_mode;
+
+ // If stepMode is mixed, alternate between vertex and instance.
+ if (stepMode === 'mixed') {
+ stepMode = ['vertex', 'instance'][b % 2];
+ }
+
+ let vertexOrInstanceCount;
+ switch (stepMode) {
+ case undefined:
+ case 'vertex':
+ vertexOrInstanceCount = vertexCount;
+ break;
+ case 'instance':
+ vertexOrInstanceCount = instanceCount;
+ break;
+ }
+
+ const attributes = [];
+ const numAttributesForBuffer = Math.min(
+ maxAttributesPerVertexBuffer,
+ maxAttributes - b * maxAttributesPerVertexBuffer
+ );
+
+ for (let a = 0; a < numAttributesForBuffer; ++a) {
+ const attribute = {
+ format: t.params.vertex_format,
+ shaderLocation,
+ offset
+ };
+ attributes.push(attribute);
+
+ offset += ExpectedDataConstructor.BYTES_PER_ELEMENT;
+ shaderLocation += 1;
+ }
+
+ for (let v = 0; v < vertexOrInstanceCount; ++v) {
+ for (let a = 0; a < numAttributesForBuffer; ++a) {
+ vertexBufferValues.push(attributeValue);
+ attributeValue += 1.234; // Values will get rounded later if we make a Uint32Array.
+ }
+ }
+
+ bufferLayouts.push({
+ attributes,
+ arrayStride: offset,
+ stepMode
+ });
+
+ const data = new ExpectedDataConstructor(vertexBufferValues);
+ vertexBufferData.push(data);
+ vertexBuffers.push(t.makeBufferWithContents(data, GPUBufferUsage.VERTEX));
+ }
+
+ // Create an array of shader locations [0, 1, 2, 3, ...] for easy iteration.
+ const vertexInputShaderLocations = new Array(shaderLocation).fill(0).map((_, i) => i);
+
+ // Create the expected data buffer.
+ const expectedData = new ExpectedDataConstructor(
+ vertexCount * instanceCount * vertexInputShaderLocations.length
+ );
+
+ // Populate the expected data. This is a CPU-side version of what we expect the shader
+ // to do.
+ for (let vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) {
+ for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
+ bufferLayouts.forEach((bufferLayout, b) => {
+ for (const attribute of bufferLayout.attributes) {
+ const primitiveId = vertexCount * instanceIndex + vertexIndex;
+ const outputIndex =
+ primitiveId * vertexInputShaderLocations.length + attribute.shaderLocation;
+
+ let vertexOrInstanceIndex;
+ switch (bufferLayout.stepMode) {
+ case undefined:
+ case 'vertex':
+ vertexOrInstanceIndex = vertexIndex;
+ break;
+ case 'instance':
+ vertexOrInstanceIndex = instanceIndex;
+ break;
+ }
+
+ const view = new ExpectedDataConstructor(
+ vertexBufferData[b].buffer,
+ bufferLayout.arrayStride * vertexOrInstanceIndex + attribute.offset,
+ 1
+ );
+ expectedData[outputIndex] = view[0];
+ }
+ });
+ }
+ }
+
+ let wgslFormat;
+ switch (t.params.vertex_format) {
+ case 'uint32':
+ wgslFormat = 'u32';
+ break;
+ case 'float32':
+ wgslFormat = 'f32';
+ break;
+ }
+
+ // Maximum inter-stage shader location is 14, and we need to consume one for primitiveId, 12 for
+ // location 0 to 11, and combine the remaining vertex inputs into one location (one
+ // vec4<wgslFormat> when vertex_attribute_count === 16).
+ const interStageScalarShaderLocation = Math.min(shaderLocation, 12);
+ const interStageScalarShaderLocations = new Array(interStageScalarShaderLocation).
+ fill(0).
+ map((_, i) => i);
+
+ let accumulateVariableDeclarationsInVertexShader = '';
+ let accumulateVariableAssignmentsInVertexShader = '';
+ let accumulateVariableDeclarationsInFragmentShader = '';
+ let accumulateVariableAssignmentsInFragmentShader = '';
+ // The remaining 3 vertex attributes
+ if (numAttributes === 16) {
+ accumulateVariableDeclarationsInVertexShader = `
+ @location(13) @interpolate(flat) outAttrib13 : vec4<${wgslFormat}>,
+ `;
+ accumulateVariableAssignmentsInVertexShader = `
+ output.outAttrib13 =
+ vec4<${wgslFormat}>(input.attrib12, input.attrib13, input.attrib14, input.attrib15);
+ `;
+ accumulateVariableDeclarationsInFragmentShader = `
+ @location(13) @interpolate(flat) attrib13 : vec4<${wgslFormat}>,
+ `;
+ accumulateVariableAssignmentsInFragmentShader = `
+ outBuffer.primitives[input.primitiveId].attrib12 = input.attrib13.x;
+ outBuffer.primitives[input.primitiveId].attrib13 = input.attrib13.y;
+ outBuffer.primitives[input.primitiveId].attrib14 = input.attrib13.z;
+ outBuffer.primitives[input.primitiveId].attrib15 = input.attrib13.w;
+ `;
+ } else if (numAttributes === 14) {
+ accumulateVariableDeclarationsInVertexShader = `
+ @location(13) @interpolate(flat) outAttrib13 : vec4<${wgslFormat}>,
+ `;
+ accumulateVariableAssignmentsInVertexShader = `
+ output.outAttrib13 =
+ vec4<${wgslFormat}>(input.attrib12, input.attrib13, 0, 0);
+ `;
+ accumulateVariableDeclarationsInFragmentShader = `
+ @location(13) @interpolate(flat) attrib13 : vec4<${wgslFormat}>,
+ `;
+ accumulateVariableAssignmentsInFragmentShader = `
+ outBuffer.primitives[input.primitiveId].attrib12 = input.attrib13.x;
+ outBuffer.primitives[input.primitiveId].attrib13 = input.attrib13.y;
+ `;
+ }
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+struct Inputs {
+ @builtin(vertex_index) vertexIndex : u32,
+ @builtin(instance_index) instanceIndex : u32,
+${vertexInputShaderLocations.map((i) => ` @location(${i}) attrib${i} : ${wgslFormat},`).join('\n')}
+};
+
+struct Outputs {
+ @builtin(position) Position : vec4<f32>,
+${interStageScalarShaderLocations.
+ map((i) => ` @location(${i}) @interpolate(flat) outAttrib${i} : ${wgslFormat},`).
+ join('\n')}
+ @location(${interStageScalarShaderLocations.length}) @interpolate(flat) primitiveId : u32,
+${accumulateVariableDeclarationsInVertexShader}
+};
+
+@vertex fn main(input : Inputs) -> Outputs {
+ var output : Outputs;
+${interStageScalarShaderLocations.map((i) => ` output.outAttrib${i} = input.attrib${i};`).join('\n')}
+${accumulateVariableAssignmentsInVertexShader}
+
+ output.primitiveId = input.instanceIndex * ${instanceCount}u + input.vertexIndex;
+ output.Position = vec4<f32>(0.0, 0.0, 0.5, 1.0);
+ return output;
+}
+ `
+ }),
+ entryPoint: 'main',
+ buffers: bufferLayouts
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+struct Inputs {
+${interStageScalarShaderLocations.
+ map((i) => ` @location(${i}) @interpolate(flat) attrib${i} : ${wgslFormat},`).
+ join('\n')}
+ @location(${interStageScalarShaderLocations.length}) @interpolate(flat) primitiveId : u32,
+${accumulateVariableDeclarationsInFragmentShader}
+};
+
+struct OutPrimitive {
+${vertexInputShaderLocations.map((i) => ` attrib${i} : ${wgslFormat},`).join('\n')}
+};
+struct OutBuffer {
+ primitives : array<OutPrimitive>
+};
+@group(0) @binding(0) var<storage, read_write> outBuffer : OutBuffer;
+
+@fragment fn main(input : Inputs) {
+${interStageScalarShaderLocations.
+ map((i) => ` outBuffer.primitives[input.primitiveId].attrib${i} = input.attrib${i};`).
+ join('\n')}
+${accumulateVariableAssignmentsInFragmentShader}
+}
+ `
+ }),
+ entryPoint: 'main',
+ targets: [
+ {
+ format: 'rgba8unorm',
+ writeMask: 0
+ }]
+
+ },
+ primitive: {
+ topology: 'point-list'
+ }
+ });
+
+ const resultBuffer = t.device.createBuffer({
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ size: vertexCount * instanceCount * vertexInputShaderLocations.length * 4
+ });
+
+ const resultBindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: resultBuffer
+ }
+ }]
+
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ // Dummy render attachment - not used (WebGPU doesn't allow using a render pass with no
+ // attachments)
+ view: t.device.
+ createTexture({
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [1],
+ format: 'rgba8unorm'
+ }).
+ createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(0, resultBindGroup);
+ for (let i = 0; i < t.params.vertex_buffer_count; ++i) {
+ renderPass.setVertexBuffer(i, vertexBuffers[i]);
+ }
+ renderPass.draw(vertexCount, instanceCount);
+ renderPass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(resultBuffer, expectedData);
+});
+
+g.test('vertex_attributes,formats').
+desc(
+ `Test all vertex formats are fetched correctly.
+
+ Runs a basic vertex shader which loads vertex data from two attributes which
+ may have different formats. Write data out to a storage buffer and check that
+ it was loaded correctly.
+
+ Params:
+ - vertex_format_1={...all_vertex_formats}
+ - vertex_format_2={...all_vertex_formats}
+ `
+).
+unimplemented();
+
+g.test(`largeish_buffer`).
+desc(
+ `
+ Test a very large range of buffer is bound.
+ For a render pipeline that use a vertex step mode and a instance step mode vertex buffer, test
+ that :
+ - For draw, drawIndirect, drawIndexed and drawIndexedIndirect:
+ - The bound range of vertex step mode vertex buffer is significantly larger than necessary
+ - The bound range of instance step mode vertex buffer is significantly larger than necessary
+ - A large buffer is bound to an unused slot
+ - For drawIndexed and drawIndexedIndirect:
+ - The bound range of index buffer is significantly larger than necessary
+ - For drawIndirect and drawIndexedIndirect:
+ - The indirect buffer is significantly larger than necessary
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/indirect_draw.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/indirect_draw.spec.js
new file mode 100644
index 0000000000..4bc9cf5727
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/indirect_draw.spec.js
@@ -0,0 +1,242 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for the indirect-specific aspects of drawIndirect/drawIndexedIndirect.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ kDrawIndirectParametersSize,
+ kDrawIndexedIndirectParametersSize } from
+'../../../capability_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+
+const filled = new Uint8Array([0, 255, 0, 255]);
+const notFilled = new Uint8Array([0, 0, 0, 0]);
+
+const kRenderTargetFormat = 'rgba8unorm';
+
+class F extends GPUTest {
+ MakeIndexBuffer() {
+ return this.makeBufferWithContents(
+ new Uint32Array([
+ 0, 1, 2, // The bottom left triangle
+ 1, 2, 3 // The top right triangle
+ ]),
+ GPUBufferUsage.INDEX
+ );
+ }
+
+ MakeVertexBuffer(isIndexed) {
+
+ const vertices = isIndexed ?
+ [
+ -1.0, -1.0,
+ -1.0, 1.0,
+ 1.0, -1.0,
+ 1.0, 1.0] :
+
+ [
+ // The bottom left triangle
+ -1.0, 1.0,
+ 1.0, -1.0,
+ -1.0, -1.0,
+
+ // The top right triangle
+ -1.0, 1.0,
+ 1.0, -1.0,
+ 1.0, 1.0];
+
+ return this.makeBufferWithContents(new Float32Array(vertices), GPUBufferUsage.VERTEX);
+ }
+
+ MakeIndirectBuffer(isIndexed, indirectOffset) {
+ const o = indirectOffset / Uint32Array.BYTES_PER_ELEMENT;
+
+ const parametersSize = isIndexed ?
+ kDrawIndexedIndirectParametersSize :
+ kDrawIndirectParametersSize;
+ const arraySize = o + parametersSize * 2;
+
+ const indirectBuffer = [...Array(arraySize)].map(() => Math.floor(Math.random() * 100));
+
+ if (isIndexed) {
+ // draw args that will draw the left bottom triangle (expected call)
+ indirectBuffer[o] = 3; // indexCount
+ indirectBuffer[o + 1] = 1; // instanceCount
+ indirectBuffer[o + 2] = 0; // firstIndex
+ indirectBuffer[o + 3] = 0; // baseVertex
+ indirectBuffer[o + 4] = 0; // firstInstance
+
+ // draw args that will draw both triangles
+ indirectBuffer[o + 5] = 6; // indexCount
+ indirectBuffer[o + 6] = 1; // instanceCount
+ indirectBuffer[o + 7] = 0; // firstIndex
+ indirectBuffer[o + 8] = 0; // baseVertex
+ indirectBuffer[o + 9] = 0; // firstInstance
+
+ if (o >= parametersSize) {
+ // draw args that will draw the right top triangle
+ indirectBuffer[o - 5] = 3; // indexCount
+ indirectBuffer[o - 4] = 1; // instanceCount
+ indirectBuffer[o - 3] = 3; // firstIndex
+ indirectBuffer[o - 2] = 0; // baseVertex
+ indirectBuffer[o - 1] = 0; // firstInstance
+ }
+
+ if (o >= parametersSize * 2) {
+ // draw args that will draw nothing
+ indirectBuffer[0] = 0; // indexCount
+ indirectBuffer[1] = 0; // instanceCount
+ indirectBuffer[2] = 0; // firstIndex
+ indirectBuffer[3] = 0; // baseVertex
+ indirectBuffer[4] = 0; // firstInstance
+ }
+ } else {
+ // draw args that will draw the left bottom triangle (expected call)
+ indirectBuffer[o] = 3; // vertexCount
+ indirectBuffer[o + 1] = 1; // instanceCount
+ indirectBuffer[o + 2] = 0; // firstVertex
+ indirectBuffer[o + 3] = 0; // firstInstance
+
+ // draw args that will draw both triangles
+ indirectBuffer[o + 4] = 6; // vertexCount
+ indirectBuffer[o + 5] = 1; // instanceCount
+ indirectBuffer[o + 6] = 0; // firstVertex
+ indirectBuffer[o + 7] = 0; // firstInstance
+
+ if (o >= parametersSize) {
+ // draw args that will draw the right top triangle
+ indirectBuffer[o - 4] = 3; // vertexCount
+ indirectBuffer[o - 3] = 1; // instanceCount
+ indirectBuffer[o - 2] = 3; // firstVertex
+ indirectBuffer[o - 1] = 0; // firstInstance
+ }
+
+ if (o >= parametersSize * 2) {
+ // draw args that will draw nothing
+ indirectBuffer[0] = 0; // vertexCount
+ indirectBuffer[1] = 0; // instanceCount
+ indirectBuffer[2] = 0; // firstVertex
+ indirectBuffer[3] = 0; // firstInstance
+ }
+ }
+
+ return this.makeBufferWithContents(new Uint32Array(indirectBuffer), GPUBufferUsage.INDIRECT);
+ }
+}
+
+export const g = makeTestGroup(TextureTestMixin(F));
+
+g.test('basics').
+desc(
+ `Test that the indirect draw parameters are tightly packed for drawIndirect and drawIndexedIndirect.
+An indirectBuffer is created based on indirectOffset. The actual draw args being used indicated by the
+indirectOffset is going to draw a left bottom triangle.
+While the remaining indirectBuffer is populated with random numbers or draw args
+that draw right top triangle, both, or nothing which will fail the color check.
+The test will check render target to see if only the left bottom area is filled,
+meaning the expected draw args is uploaded correctly by the indirectBuffer and indirectOffset.
+
+Params:
+ - draw{Indirect, IndexedIndirect}
+ - indirectOffset= {0, 4, k * sizeof(args struct), k * sizeof(args struct) + 4}
+ `
+).
+params((u) =>
+u.
+combine('isIndexed', [true, false]).
+beginSubcases().
+expand('indirectOffset', (p) => {
+ const indirectDrawParametersSize = p.isIndexed ?
+ kDrawIndexedIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT :
+ kDrawIndirectParametersSize * Uint32Array.BYTES_PER_ELEMENT;
+ return [
+ 0,
+ Uint32Array.BYTES_PER_ELEMENT,
+ 1 * indirectDrawParametersSize,
+ 1 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT,
+ 3 * indirectDrawParametersSize,
+ 3 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT,
+ 99 * indirectDrawParametersSize,
+ 99 * indirectDrawParametersSize + Uint32Array.BYTES_PER_ELEMENT];
+
+})
+).
+fn((t) => {
+ const { isIndexed, indirectOffset } = t.params;
+
+ const vertexBuffer = t.MakeVertexBuffer(isIndexed);
+ const indirectBuffer = t.MakeIndirectBuffer(isIndexed, indirectOffset);
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `@vertex fn main(@location(0) pos : vec2<f32>) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(pos, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ attributes: [
+ {
+ shaderLocation: 0,
+ format: 'float32x2',
+ offset: 0
+ }],
+
+ arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT
+ }]
+
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [
+ {
+ format: kRenderTargetFormat
+ }]
+
+ }
+ });
+
+ const renderTarget = t.device.createTexture({
+ size: [4, 4],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ format: kRenderTargetFormat
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(pipeline);
+ renderPass.setVertexBuffer(0, vertexBuffer, 0);
+
+ if (isIndexed) {
+ renderPass.setIndexBuffer(t.MakeIndexBuffer(), 'uint32', 0);
+ renderPass.drawIndexedIndirect(indirectBuffer, indirectOffset);
+ } else {
+ renderPass.drawIndirect(indirectBuffer, indirectOffset);
+ }
+ renderPass.end();
+ t.queue.submit([commandEncoder.finish()]);
+
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: renderTarget }, [
+ // The bottom left area is filled
+ { coord: { x: 0, y: 1 }, exp: filled },
+ // The top right area is not filled
+ { coord: { x: 1, y: 0 }, exp: notFilled }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/robust_access_index.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/robust_access_index.spec.js
new file mode 100644
index 0000000000..0e5c1fa8af
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/robust_access_index.spec.js
@@ -0,0 +1,8 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+TODO: Test that drawIndexedIndirect accesses the index buffer robustly.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/stencil.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/stencil.spec.js
new file mode 100644
index 0000000000..9868fa37c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/rendering/stencil.spec.js
@@ -0,0 +1,584 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test related to stencil states, stencil op, compare func, etc.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+
+import {
+ kDepthStencilFormats,
+ kTextureFormatInfo } from
+
+'../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+const kStencilFormats = kDepthStencilFormats.filter((format) => kTextureFormatInfo[format].stencil);
+
+const kBaseColor = new Float32Array([1.0, 1.0, 1.0, 1.0]);
+const kRedStencilColor = new Float32Array([1.0, 0.0, 0.0, 1.0]);
+const kGreenStencilColor = new Float32Array([0.0, 1.0, 0.0, 1.0]);
+
+
+
+
+
+
+
+class StencilTest extends TextureTestMixin(GPUTest) {
+ checkStencilOperation(
+ depthStencilFormat,
+ testStencilState,
+ initialStencil,
+ _expectedStencil,
+ depthCompare = 'always')
+ {
+ const kReferenceStencil = 3;
+
+ const baseStencilState = {
+ compare: 'always',
+ failOp: 'keep',
+ passOp: 'replace'
+ };
+
+ const stencilState = {
+ compare: 'equal',
+ failOp: 'keep',
+ passOp: 'keep'
+ };
+
+ const baseState = {
+ format: depthStencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: baseStencilState,
+ stencilBack: baseStencilState
+ };
+
+ const testState = {
+ format: depthStencilFormat,
+ depthWriteEnabled: false,
+ depthCompare,
+ stencilFront: testStencilState,
+ stencilBack: testStencilState
+ };
+
+ const testState2 = {
+ format: depthStencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: stencilState,
+ stencilBack: stencilState
+ };
+
+ const testStates = [
+ // Draw the base triangle with stencil reference 1. This clears the stencil buffer to 1.
+ { state: baseState, color: kBaseColor, stencil: initialStencil },
+ { state: testState, color: kRedStencilColor, stencil: kReferenceStencil },
+ { state: testState2, color: kGreenStencilColor, stencil: _expectedStencil }];
+
+ this.runStencilStateTest(depthStencilFormat, testStates, kGreenStencilColor);
+ }
+
+ checkStencilCompareFunction(
+ depthStencilFormat,
+ compareFunction,
+ stencilRefValue,
+ expectedColor)
+ {
+ const baseStencilState = {
+ compare: 'always',
+ failOp: 'keep',
+ passOp: 'replace'
+ };
+
+ const stencilState = {
+ compare: compareFunction,
+ failOp: 'keep',
+ passOp: 'keep'
+ };
+
+ const baseState = {
+ format: depthStencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: baseStencilState,
+ stencilBack: baseStencilState
+ };
+
+ const testState = {
+ format: depthStencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: stencilState,
+ stencilBack: stencilState
+ };
+
+ const testStates = [
+ // Draw the base triangle with stencil reference 1. This clears the stencil buffer to 1.
+ { state: baseState, color: kBaseColor, stencil: 1 },
+ { state: testState, color: kGreenStencilColor, stencil: stencilRefValue }];
+
+ this.runStencilStateTest(depthStencilFormat, testStates, expectedColor);
+ }
+
+ runStencilStateTest(
+ depthStencilFormat,
+ testStates,
+ expectedColor,
+ isSingleEncoderMultiplePass = false)
+ {
+ const renderTargetFormat = 'rgba8unorm';
+ const renderTarget = this.trackForCleanup(
+ this.device.createTexture({
+ format: renderTargetFormat,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const depthTexture = this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: depthStencilFormat,
+ sampleCount: 1,
+ mipLevelCount: 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST
+ })
+ );
+
+ const hasDepth = kTextureFormatInfo[depthStencilFormat].depth;
+ const depthStencilAttachment = {
+ view: depthTexture.createView(),
+ depthLoadOp: hasDepth ? 'load' : undefined,
+ depthStoreOp: hasDepth ? 'store' : undefined,
+ stencilLoadOp: 'load',
+ stencilStoreOp: 'store'
+ };
+
+ const encoder = this.device.createCommandEncoder();
+ let pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ loadOp: 'load'
+ }],
+
+ depthStencilAttachment
+ });
+
+ if (isSingleEncoderMultiplePass) {
+ pass.end();
+ }
+
+ // Draw a triangle with the given stencil reference and the comparison function.
+ // The color will be kGreenStencilColor if the stencil test passes, and kBaseColor if not.
+ for (const test of testStates) {
+ if (isSingleEncoderMultiplePass) {
+ pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ storeOp: 'store',
+ loadOp: 'load'
+ }],
+
+ depthStencilAttachment
+ });
+ }
+ const testPipeline = this.createRenderPipelineForTest(test.state);
+ pass.setPipeline(testPipeline);
+ if (test.stencil !== undefined) {
+ pass.setStencilReference(test.stencil);
+ }
+ pass.setBindGroup(
+ 0,
+ this.createBindGroupForTest(testPipeline.getBindGroupLayout(0), test.color)
+ );
+ pass.draw(1);
+
+ if (isSingleEncoderMultiplePass) {
+ pass.end();
+ }
+ }
+
+ if (!isSingleEncoderMultiplePass) {
+ pass.end();
+ }
+ this.device.queue.submit([encoder.finish()]);
+
+ const expColor = {
+ R: expectedColor[0],
+ G: expectedColor[1],
+ B: expectedColor[2],
+ A: expectedColor[3]
+ };
+ const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, (_coords) => expColor);
+ this.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, [1, 1]);
+ }
+
+ createRenderPipelineForTest(depthStencil) {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ targets: [{ format: 'rgba8unorm' }],
+ module: this.device.createShaderModule({
+ code: `
+ struct Params {
+ color : vec4<f32>
+ }
+ @group(0) @binding(0) var<uniform> params : Params;
+
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(params.color);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ primitive: { topology: 'point-list' },
+ depthStencil
+ });
+ }
+
+ createBindGroupForTest(layout, data) {
+ return this.device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: this.makeBufferWithContents(data, GPUBufferUsage.UNIFORM)
+ }
+ }]
+
+ });
+ }
+}
+
+export const g = makeTestGroup(StencilTest);
+
+g.test('stencil_compare_func').
+desc(
+ `
+ Tests that stencil comparison functions with the stencil reference value works as expected.
+ `
+).
+params((u) =>
+u //
+.combine('format', kStencilFormats).
+combineWithParams([
+{ stencilCompare: 'always', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'always', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'always', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'equal', stencilRefValue: 0, _expectedColor: kBaseColor },
+{ stencilCompare: 'equal', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'equal', stencilRefValue: 2, _expectedColor: kBaseColor },
+{ stencilCompare: 'greater', stencilRefValue: 0, _expectedColor: kBaseColor },
+{ stencilCompare: 'greater', stencilRefValue: 1, _expectedColor: kBaseColor },
+{ stencilCompare: 'greater', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'greater-equal', stencilRefValue: 0, _expectedColor: kBaseColor },
+{ stencilCompare: 'greater-equal', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'greater-equal', stencilRefValue: 2, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'less', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'less', stencilRefValue: 1, _expectedColor: kBaseColor },
+{ stencilCompare: 'less', stencilRefValue: 2, _expectedColor: kBaseColor },
+{ stencilCompare: 'less-equal', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'less-equal', stencilRefValue: 1, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'less-equal', stencilRefValue: 2, _expectedColor: kBaseColor },
+{ stencilCompare: 'never', stencilRefValue: 0, _expectedColor: kBaseColor },
+{ stencilCompare: 'never', stencilRefValue: 1, _expectedColor: kBaseColor },
+{ stencilCompare: 'never', stencilRefValue: 2, _expectedColor: kBaseColor },
+{ stencilCompare: 'not-equal', stencilRefValue: 0, _expectedColor: kGreenStencilColor },
+{ stencilCompare: 'not-equal', stencilRefValue: 1, _expectedColor: kBaseColor },
+{ stencilCompare: 'not-equal', stencilRefValue: 2, _expectedColor: kGreenStencilColor }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format, stencilCompare, stencilRefValue, _expectedColor } = t.params;
+
+ t.checkStencilCompareFunction(format, stencilCompare, stencilRefValue, _expectedColor);
+});
+
+g.test('stencil_passOp_operation').
+desc(
+ `
+ Test that the stencil operation is executed on stencil pass. A triangle is drawn with the 'always'
+ comparison function, so it should pass. Then, test that each pass stencil operation works with the
+ given stencil values correctly as expected. For example,
+ - If the pass operation is 'keep', it keeps the initial stencil value.
+ - If the pass operation is 'replace', it replaces the initial stencil value with the reference
+ stencil value.
+ `
+).
+params((u) =>
+u //
+.combine('format', kStencilFormats).
+combineWithParams([
+{ passOp: 'keep', initialStencil: 1, _expectedStencil: 1 },
+{ passOp: 'zero', initialStencil: 1, _expectedStencil: 0 },
+{ passOp: 'replace', initialStencil: 1, _expectedStencil: 3 },
+{ passOp: 'invert', initialStencil: 0xf0, _expectedStencil: 0x0f },
+{ passOp: 'increment-clamp', initialStencil: 1, _expectedStencil: 2 },
+{ passOp: 'increment-clamp', initialStencil: 0xff, _expectedStencil: 0xff },
+{ passOp: 'increment-wrap', initialStencil: 1, _expectedStencil: 2 },
+{ passOp: 'increment-wrap', initialStencil: 0xff, _expectedStencil: 0 },
+{ passOp: 'decrement-clamp', initialStencil: 1, _expectedStencil: 0 },
+{ passOp: 'decrement-clamp', initialStencil: 0, _expectedStencil: 0 },
+{ passOp: 'decrement-wrap', initialStencil: 1, _expectedStencil: 0 },
+{ passOp: 'decrement-wrap', initialStencil: 0, _expectedStencil: 0xff }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format, passOp, initialStencil, _expectedStencil } = t.params;
+
+ const stencilState = {
+ compare: 'always',
+ failOp: 'keep',
+ passOp
+ };
+
+ t.checkStencilOperation(format, stencilState, initialStencil, _expectedStencil);
+});
+
+g.test('stencil_failOp_operation').
+desc(
+ `
+ Test that the stencil operation is executed on stencil fail. A triangle is drawn with the 'never'
+ comparison function, so it should fail. Then, test that each fail stencil operation works with the
+ given stencil values correctly as expected. For example,
+ - If the fail operation is 'keep', it keeps the initial stencil value.
+ - If the fail operation is 'replace', it replaces the initial stencil value with the reference
+ stencil value.
+ `
+).
+params((u) =>
+u //
+.combine('format', kStencilFormats).
+combineWithParams([
+{ failOp: 'keep', initialStencil: 1, _expectedStencil: 1 },
+{ failOp: 'zero', initialStencil: 1, _expectedStencil: 0 },
+{ failOp: 'replace', initialStencil: 1, _expectedStencil: 3 },
+{ failOp: 'invert', initialStencil: 0xf0, _expectedStencil: 0x0f },
+{ failOp: 'increment-clamp', initialStencil: 1, _expectedStencil: 2 },
+{ failOp: 'increment-clamp', initialStencil: 0xff, _expectedStencil: 0xff },
+{ failOp: 'increment-wrap', initialStencil: 1, _expectedStencil: 2 },
+{ failOp: 'increment-wrap', initialStencil: 0xff, _expectedStencil: 0 },
+{ failOp: 'decrement-clamp', initialStencil: 1, _expectedStencil: 0 },
+{ failOp: 'decrement-clamp', initialStencil: 0, _expectedStencil: 0 },
+{ failOp: 'decrement-wrap', initialStencil: 2, _expectedStencil: 1 },
+{ failOp: 'decrement-wrap', initialStencil: 1, _expectedStencil: 0 },
+{ failOp: 'decrement-wrap', initialStencil: 0, _expectedStencil: 0xff }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format, failOp, initialStencil, _expectedStencil } = t.params;
+
+ const stencilState = {
+ compare: 'never',
+ failOp,
+ passOp: 'keep'
+ };
+
+ // Draw the base triangle with stencil reference 1. This clears the stencil buffer to 1.
+ // Always fails because the comparison never passes. Therefore red is never drawn, and the
+ // stencil contents may be updated according to `operation`.
+ t.checkStencilOperation(format, stencilState, initialStencil, _expectedStencil);
+});
+
+g.test('stencil_depthFailOp_operation').
+desc(
+ `
+ Test that the stencil operation is executed on depthCompare fail. A triangle is drawn with the
+ 'never' depthCompare, so it should fail the depth test. Then, test that each 'depthFailOp' stencil operation
+ works with the given stencil values correctly as expected. For example,
+ - If the depthFailOp operation is 'keep', it keeps the initial stencil value.
+ - If the depthFailOp operation is 'replace', it replaces the initial stencil value with the
+ reference stencil value.
+ `
+).
+params((u) =>
+u //
+.combine(
+ 'format',
+ kDepthStencilFormats.filter((format) => {
+ const info = kTextureFormatInfo[format];
+ return info.depth && info.stencil;
+ })
+).
+combineWithParams([
+{ depthFailOp: 'keep', initialStencil: 1, _expectedStencil: 1 },
+{ depthFailOp: 'zero', initialStencil: 1, _expectedStencil: 0 },
+{ depthFailOp: 'replace', initialStencil: 1, _expectedStencil: 3 },
+{ depthFailOp: 'invert', initialStencil: 0xf0, _expectedStencil: 0x0f },
+{ depthFailOp: 'increment-clamp', initialStencil: 1, _expectedStencil: 2 },
+{ depthFailOp: 'increment-clamp', initialStencil: 0xff, _expectedStencil: 0xff },
+{ depthFailOp: 'increment-wrap', initialStencil: 1, _expectedStencil: 2 },
+{ depthFailOp: 'increment-wrap', initialStencil: 0xff, _expectedStencil: 0 },
+{ depthFailOp: 'decrement-clamp', initialStencil: 1, _expectedStencil: 0 },
+{ depthFailOp: 'decrement-clamp', initialStencil: 0, _expectedStencil: 0 },
+{ depthFailOp: 'decrement-wrap', initialStencil: 2, _expectedStencil: 1 },
+{ depthFailOp: 'decrement-wrap', initialStencil: 1, _expectedStencil: 0 },
+{ depthFailOp: 'decrement-wrap', initialStencil: 0, _expectedStencil: 0xff }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format, depthFailOp, initialStencil, _expectedStencil } = t.params;
+
+ const stencilState = {
+ compare: 'always',
+ failOp: 'keep',
+ passOp: 'keep',
+ depthFailOp
+ };
+
+ // Call checkStencilOperation function with enabling the depthTest to test that the depthFailOp
+ // stencil operation works as expected.
+ t.checkStencilOperation(format, stencilState, initialStencil, _expectedStencil, 'never');
+});
+
+g.test('stencil_read_write_mask').
+desc(
+ `
+ Tests that setting a stencil read/write masks work. Basically, The base triangle sets 3 to the
+ stencil, and then try to draw a triangle with different stencil values.
+ - In case that 'write' mask is 1,
+ * If the stencil of the triangle is 1, it draws because
+ 'base stencil(3) & write mask(1) == triangle stencil(1)'.
+ * If the stencil of the triangle is 2, it does not draw because
+ 'base stencil(3) & write mask(1) != triangle stencil(2)'.
+
+ - In case that 'read' mask is 2,
+ * If the stencil of the triangle is 1, it does not draw because
+ 'base stencil(3) & read mask(2) != triangle stencil(1)'.
+ * If the stencil of the triangle is 2, it draws because
+ 'base stencil(3) & read mask(2) == triangle stencil(2)'.
+ `
+).
+params((u) =>
+u //
+.combine('format', kStencilFormats).
+combineWithParams([
+{ maskType: 'write', stencilRefValue: 1, _expectedColor: kRedStencilColor },
+{ maskType: 'write', stencilRefValue: 2, _expectedColor: kBaseColor },
+{ maskType: 'read', stencilRefValue: 1, _expectedColor: kBaseColor },
+{ maskType: 'read', stencilRefValue: 2, _expectedColor: kRedStencilColor }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format, maskType, stencilRefValue, _expectedColor } = t.params;
+
+ const baseStencilState = {
+ compare: 'always',
+ failOp: 'keep',
+ passOp: 'replace'
+ };
+
+ const stencilState = {
+ compare: 'equal',
+ failOp: 'keep',
+ passOp: 'keep'
+ };
+
+ const baseState = {
+ format,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: baseStencilState,
+ stencilBack: baseStencilState,
+ stencilReadMask: 0xff,
+ stencilWriteMask: maskType === 'write' ? 0x1 : 0xff
+ };
+
+ const testState = {
+ format,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: stencilState,
+ stencilBack: stencilState,
+ stencilReadMask: maskType === 'read' ? 0x2 : 0xff,
+ stencilWriteMask: 0xff
+ };
+
+ const testStates = [
+ // Draw the base triangle with stencil reference 3. This clears the stencil buffer to 3.
+ { state: baseState, color: kBaseColor, stencil: 3 },
+ { state: testState, color: kRedStencilColor, stencil: stencilRefValue }];
+
+
+ t.runStencilStateTest(format, testStates, _expectedColor);
+});
+
+g.test('stencil_reference_initialized').
+desc('Test that stencil reference is initialized as zero for new render pass.').
+params((u) => u.combine('format', kStencilFormats)).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format } = t.params;
+
+ const baseStencilState = {
+ compare: 'always',
+ passOp: 'replace'
+ };
+
+ const testStencilState = {
+ compare: 'equal',
+ passOp: 'keep'
+ };
+
+ const hasDepth = !!kTextureFormatInfo[format].depth;
+
+ const baseState = {
+ format,
+ depthWriteEnabled: hasDepth,
+ depthCompare: 'always',
+ stencilFront: baseStencilState,
+ stencilBack: baseStencilState
+ };
+
+ const testState = {
+ format,
+ depthWriteEnabled: hasDepth,
+ depthCompare: 'always',
+ stencilFront: testStencilState,
+ stencilBack: testStencilState
+ };
+
+ // First pass sets the stencil to 0x1, the second pass sets the stencil to its default
+ // value, and the third pass tests if the stencil is zero.
+ const testStates = [
+ { state: baseState, color: kBaseColor, stencil: 0x1 },
+ { state: baseState, color: kRedStencilColor, stencil: undefined },
+ { state: testState, color: kGreenStencilColor, stencil: 0x0 }];
+
+
+ // The third draw should pass the stencil test since the second pass set it to default zero.
+ t.runStencilStateTest(format, testStates, kGreenStencilColor, true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/buffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/buffer.spec.js
new file mode 100644
index 0000000000..fa7cbdbbba
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/buffer.spec.js
@@ -0,0 +1,899 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { makeTestGroup } from '../../../../common/framework/test_group.js';import { unreachable } from '../../../../common/util/util.js';import { GPUConst } from '../../../constants.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { getTextureCopyLayout } from '../../../util/texture/layout.js';
+
+
+export const description = `
+Test uninitialized buffers are initialized to zero when read
+(or read-written, e.g. with depth write or atomics).
+
+Note that:
+- We don't need 'copy_buffer_to_buffer_copy_destination' here because there has already been an
+ operation test 'command_buffer.copyBufferToBuffer.single' that provides the same functionality.
+`;
+
+const kMapModeOptions = [GPUConst.MapMode.READ, GPUConst.MapMode.WRITE];
+const kBufferUsagesForMappedAtCreationTests = [
+GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.MAP_READ,
+GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.MAP_WRITE,
+GPUConst.BufferUsage.COPY_SRC];
+
+
+class F extends GPUTest {
+ GetBufferUsageFromMapMode(mapMode) {
+ switch (mapMode) {
+ case GPUMapMode.READ:
+ return GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ;
+ case GPUMapMode.WRITE:
+ return GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE;
+ default:
+ unreachable();
+ return 0;
+ }
+ }
+
+ CheckGPUBufferContent(
+ buffer,
+ bufferUsage,
+ expectedData)
+ {
+ const mappable = bufferUsage & GPUBufferUsage.MAP_READ;
+ this.expectGPUBufferValuesEqual(buffer, expectedData, 0, { method: mappable ? 'map' : 'copy' });
+ }
+
+ TestBufferZeroInitInBindGroup(
+ computeShaderModule,
+ buffer,
+ bufferOffset,
+ boundBufferSize)
+ {
+ const computePipeline = this.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: computeShaderModule,
+ entryPoint: 'main'
+ }
+ });
+ const outputTexture = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: [1, 1, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING
+ });
+ this.trackForCleanup(outputTexture);
+ const bindGroup = this.device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ offset: bufferOffset,
+ size: boundBufferSize
+ }
+ },
+ {
+ binding: 1,
+ resource: outputTexture.createView()
+ }]
+
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const computePass = encoder.beginComputePass();
+ computePass.setBindGroup(0, bindGroup);
+ computePass.setPipeline(computePipeline);
+ computePass.dispatchWorkgroups(1);
+ computePass.end();
+ this.queue.submit([encoder.finish()]);
+
+ this.CheckBufferAndOutputTexture(buffer, boundBufferSize + bufferOffset, outputTexture);
+ }
+
+ CreateRenderPipelineForTest(
+ vertexShaderModule,
+ testVertexBuffer)
+ {
+ const renderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: vertexShaderModule,
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment
+ fn main(@location(0) i_color : vec4<f32>) -> @location(0) vec4<f32> {
+ return i_color;
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: {
+ topology: 'point-list'
+ }
+ };
+ if (testVertexBuffer) {
+ renderPipelineDescriptor.vertex.buffers = [
+ {
+ arrayStride: 16,
+ attributes: [{ format: 'float32x4', offset: 0, shaderLocation: 0 }]
+ }];
+
+ }
+
+ return this.device.createRenderPipeline(renderPipelineDescriptor);
+ }
+
+ RecordInitializeTextureColor(
+ encoder,
+ texture,
+ color)
+ {
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ clearValue: color,
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.end();
+ }
+
+ CheckBufferAndOutputTexture(
+ buffer,
+ bufferSize,
+ outputTexture,
+ outputTextureSize = [1, 1, 1],
+ outputTextureColor = { R: 0.0, G: 1.0, B: 0.0, A: 1.0 })
+ {
+ this.expectSingleColor(outputTexture, 'rgba8unorm', {
+ size: outputTextureSize,
+ exp: outputTextureColor
+ });
+
+ const expectedBufferData = new Uint8Array(bufferSize);
+ this.expectGPUBufferValuesEqual(buffer, expectedBufferData);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('partial_write_buffer').
+desc(
+ `Verify when we upload data to a part of a buffer with writeBuffer() just after the creation of
+the buffer, the remaining part of that buffer will be initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('offset', [0, 8, -12])).
+fn((t) => {
+ const { offset } = t.params;
+ const bufferSize = 32;
+ const appliedOffset = offset >= 0 ? offset : bufferSize + offset;
+
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(buffer);
+
+ const copySize = 12;
+ const writeData = new Uint8Array(copySize);
+ const expectedData = new Uint8Array(bufferSize);
+ for (let i = 0; i < copySize; ++i) {
+ expectedData[appliedOffset + i] = writeData[i] = i + 1;
+ }
+ t.queue.writeBuffer(buffer, appliedOffset, writeData, 0);
+
+ t.expectGPUBufferValuesEqual(buffer, expectedData);
+});
+
+g.test('map_whole_buffer').
+desc(
+ `Verify when we map the whole range of a mappable GPUBuffer to a typed array buffer just after
+creating the GPUBuffer, the contents of both the typed array buffer and the GPUBuffer itself
+have already been initialized to 0.`
+).
+params((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+
+ const bufferSize = 32;
+ const bufferUsage = t.GetBufferUsageFromMapMode(mapMode);
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(buffer);
+
+ await buffer.mapAsync(mapMode);
+ const readData = new Uint8Array(buffer.getMappedRange());
+ for (let i = 0; i < bufferSize; ++i) {
+ t.expect(readData[i] === 0);
+ }
+ buffer.unmap();
+
+ const expectedData = new Uint8Array(bufferSize);
+ t.CheckGPUBufferContent(buffer, bufferUsage, expectedData);
+});
+
+g.test('map_partial_buffer').
+desc(
+ `Verify when we map a subrange of a mappable GPUBuffer to a typed array buffer just after the
+creation of the GPUBuffer, the contents of both the typed array buffer and the GPUBuffer have
+already been initialized to 0.`
+).
+params((u) => u.combine('mapMode', kMapModeOptions).beginSubcases().combine('offset', [0, 8, -16])).
+fn(async (t) => {
+ const { mapMode, offset } = t.params;
+ const bufferSize = 32;
+ const appliedOffset = offset >= 0 ? offset : bufferSize + offset;
+
+ const bufferUsage = t.GetBufferUsageFromMapMode(mapMode);
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(buffer);
+
+ const expectedData = new Uint8Array(bufferSize);
+ {
+ const mapSize = 16;
+ await buffer.mapAsync(mapMode, appliedOffset, mapSize);
+ const mappedData = new Uint8Array(buffer.getMappedRange(appliedOffset, mapSize));
+ for (let i = 0; i < mapSize; ++i) {
+ t.expect(mappedData[i] === 0);
+ if (mapMode === GPUMapMode.WRITE) {
+ mappedData[i] = expectedData[appliedOffset + i] = i + 1;
+ }
+ }
+ buffer.unmap();
+ }
+
+ t.CheckGPUBufferContent(buffer, bufferUsage, expectedData);
+});
+
+g.test('mapped_at_creation_whole_buffer').
+desc(
+ `Verify when we call getMappedRange() at the whole range of a GPUBuffer created with
+mappedAtCreation === true just after its creation, the contents of both the returned typed
+array buffer of getMappedRange() and the GPUBuffer itself have all been initialized to 0.`
+).
+params((u) => u.combine('bufferUsage', kBufferUsagesForMappedAtCreationTests)).
+fn((t) => {
+ const { bufferUsage } = t.params;
+
+ const bufferSize = 32;
+ const buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size: bufferSize,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(buffer);
+
+ const mapped = new Uint8Array(buffer.getMappedRange());
+ for (let i = 0; i < bufferSize; ++i) {
+ t.expect(mapped[i] === 0);
+ }
+ buffer.unmap();
+
+ const expectedData = new Uint8Array(bufferSize);
+ t.CheckGPUBufferContent(buffer, bufferUsage, expectedData);
+});
+
+g.test('mapped_at_creation_partial_buffer').
+desc(
+ `Verify when we call getMappedRange() at a subrange of a GPUBuffer created with
+mappedAtCreation === true just after its creation, the contents of both the returned typed
+array buffer of getMappedRange() and the GPUBuffer itself have all been initialized to 0.`
+).
+params((u) =>
+u.
+combine('bufferUsage', kBufferUsagesForMappedAtCreationTests).
+beginSubcases().
+combine('offset', [0, 8, -16])
+).
+fn((t) => {
+ const { bufferUsage, offset } = t.params;
+ const bufferSize = 32;
+ const appliedOffset = offset >= 0 ? offset : bufferSize + offset;
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size: bufferSize,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(buffer);
+
+ const expectedData = new Uint8Array(bufferSize);
+ {
+ const mappedSize = 12;
+ const mapped = new Uint8Array(buffer.getMappedRange(appliedOffset, mappedSize));
+ for (let i = 0; i < mappedSize; ++i) {
+ t.expect(mapped[i] === 0);
+ if (!(bufferUsage & GPUBufferUsage.MAP_READ)) {
+ mapped[i] = expectedData[appliedOffset + i] = i + 1;
+ }
+ }
+ buffer.unmap();
+ }
+
+ t.CheckGPUBufferContent(buffer, bufferUsage, expectedData);
+});
+
+g.test('copy_buffer_to_buffer_copy_source').
+desc(
+ `Verify when the first usage of a GPUBuffer is being used as the source buffer of
+CopyBufferToBuffer(), the contents of the GPUBuffer have already been initialized to 0.`
+).
+fn((t) => {
+ const bufferSize = 32;
+ const bufferUsage = GPUBufferUsage.COPY_SRC;
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(buffer);
+
+ const expectedData = new Uint8Array(bufferSize);
+ // copyBufferToBuffer() is called inside t.CheckGPUBufferContent().
+ t.CheckGPUBufferContent(buffer, bufferUsage, expectedData);
+});
+
+g.test('copy_buffer_to_texture').
+desc(
+ `Verify when the first usage of a GPUBuffer is being used as the source buffer of
+CopyBufferToTexture(), the contents of the GPUBuffer have already been initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 8])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+ const textureSize = [8, 8, 1];
+ const dstTextureFormat = 'rgba8unorm';
+
+ const dstTexture = t.device.createTexture({
+ size: textureSize,
+ format: dstTextureFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+ t.trackForCleanup(dstTexture);
+ const layout = getTextureCopyLayout(dstTextureFormat, '2d', textureSize);
+ const srcBufferSize = layout.byteLength + bufferOffset;
+ const srcBufferUsage = GPUBufferUsage.COPY_SRC;
+ const srcBuffer = t.device.createBuffer({
+ size: srcBufferSize,
+ usage: srcBufferUsage
+ });
+ t.trackForCleanup(srcBuffer);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ {
+ buffer: srcBuffer,
+ offset: bufferOffset,
+ bytesPerRow: layout.bytesPerRow,
+ rowsPerImage: layout.rowsPerImage
+ },
+ { texture: dstTexture },
+ textureSize
+ );
+ t.queue.submit([encoder.finish()]);
+
+ t.CheckBufferAndOutputTexture(srcBuffer, srcBufferSize, dstTexture, textureSize, {
+ R: 0.0,
+ G: 0.0,
+ B: 0.0,
+ A: 0.0
+ });
+});
+
+g.test('resolve_query_set_to_partial_buffer').
+desc(
+ `Verify when we resolve a query set into a GPUBuffer just after creating that GPUBuffer, the
+remaining part of it will be initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 256])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+ const bufferSize = bufferOffset + 8;
+ const bufferUsage = GPUBufferUsage.COPY_SRC | GPUBufferUsage.QUERY_RESOLVE;
+ const dstBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(dstBuffer);
+
+ const querySet = t.device.createQuerySet({ type: 'occlusion', count: 1 });
+ const encoder = t.device.createCommandEncoder();
+ encoder.resolveQuerySet(querySet, 0, 1, dstBuffer, bufferOffset);
+ t.queue.submit([encoder.finish()]);
+
+ const expectedBufferData = new Uint8Array(bufferSize);
+ t.CheckGPUBufferContent(dstBuffer, bufferUsage, expectedBufferData);
+});
+
+g.test('copy_texture_to_partial_buffer').
+desc(
+ `Verify when we copy from a GPUTexture into a GPUBuffer just after creating that GPUBuffer, the
+remaining part of it will be initialized to 0.`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('bufferOffset', [0, 8, -16]).
+combine('arrayLayerCount', [1, 3]).
+combine('copyMipLevel', [0, 2]).
+combine('rowsPerImage', [16, 20]).
+filter((t) => {
+ // We don't need to test the copies that will cover the whole GPUBuffer.
+ return !(t.bufferOffset === 0 && t.rowsPerImage === 16);
+})
+).
+fn((t) => {
+ const { bufferOffset, arrayLayerCount, copyMipLevel, rowsPerImage } = t.params;
+ const srcTextureFormat = 'r8uint';
+ const textureSize = [32, 16, arrayLayerCount];
+
+ const srcTexture = t.device.createTexture({
+ format: srcTextureFormat,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ size: textureSize,
+ mipLevelCount: copyMipLevel + 1
+ });
+ t.trackForCleanup(srcTexture);
+
+ const bytesPerRow = 256;
+ const layout = getTextureCopyLayout(srcTextureFormat, '2d', textureSize, {
+ mipLevel: copyMipLevel,
+ bytesPerRow,
+ rowsPerImage
+ });
+
+ const dstBufferSize = layout.byteLength + Math.abs(bufferOffset);
+ const dstBuffer = t.device.createBuffer({
+ size: dstBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(dstBuffer);
+
+ const encoder = t.device.createCommandEncoder();
+
+ // Initialize srcTexture
+ for (let layer = 0; layer < arrayLayerCount; ++layer) {
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: srcTexture.createView({
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ baseMipLevel: copyMipLevel
+ }),
+ clearValue: { r: layer + 1, g: 0, b: 0, a: 0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.end();
+ }
+
+ // Do texture-to-buffer copy
+ const appliedOffset = Math.max(bufferOffset, 0);
+ encoder.copyTextureToBuffer(
+ { texture: srcTexture, mipLevel: copyMipLevel },
+ { buffer: dstBuffer, offset: appliedOffset, bytesPerRow, rowsPerImage },
+ layout.mipSize
+ );
+ t.queue.submit([encoder.finish()]);
+
+ // Check if the contents of the destination buffer are what we expect.
+ const expectedData = new Uint8Array(dstBufferSize);
+ for (let layer = 0; layer < arrayLayerCount; ++layer) {
+ for (let y = 0; y < layout.mipSize[1]; ++y) {
+ for (let x = 0; x < layout.mipSize[0]; ++x) {
+ expectedData[appliedOffset + layer * bytesPerRow * rowsPerImage + y * bytesPerRow + x] =
+ layer + 1;
+ }
+ }
+ }
+ t.expectGPUBufferValuesEqual(dstBuffer, expectedData);
+});
+
+g.test('uniform_buffer').
+desc(
+ `Verify when we use a GPUBuffer as a uniform buffer just after the creation of that GPUBuffer,
+ all the contents in that GPUBuffer have been initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 256])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+
+ const boundBufferSize = 16;
+ const buffer = t.device.createBuffer({
+ size: bufferOffset + boundBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.UNIFORM
+ });
+ t.trackForCleanup(buffer);
+
+ const computeShaderModule = t.device.createShaderModule({
+ code: `
+ struct UBO {
+ value : vec4<u32>
+ };
+ @group(0) @binding(0) var<uniform> ubo : UBO;
+ @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>;
+
+ @compute @workgroup_size(1) fn main() {
+ if (all(ubo.value == vec4<u32>(0u, 0u, 0u, 0u))) {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(0.0, 1.0, 0.0, 1.0));
+ } else {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(1.0, 0.0, 0.0, 1.0));
+ }
+ }`
+ });
+
+ // Verify the whole range of the buffer has been initialized to 0 in a compute shader.
+ t.TestBufferZeroInitInBindGroup(computeShaderModule, buffer, bufferOffset, boundBufferSize);
+});
+
+g.test('readonly_storage_buffer').
+desc(
+ `Verify when we use a GPUBuffer as a read-only storage buffer just after the creation of that
+ GPUBuffer, all the contents in that GPUBuffer have been initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 256])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+ const boundBufferSize = 16;
+ const buffer = t.device.createBuffer({
+ size: bufferOffset + boundBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+ t.trackForCleanup(buffer);
+
+ const computeShaderModule = t.device.createShaderModule({
+ code: `
+ struct SSBO {
+ value : vec4<u32>
+ };
+ @group(0) @binding(0) var<storage, read> ssbo : SSBO;
+ @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>;
+
+ @compute @workgroup_size(1) fn main() {
+ if (all(ssbo.value == vec4<u32>(0u, 0u, 0u, 0u))) {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(0.0, 1.0, 0.0, 1.0));
+ } else {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(1.0, 0.0, 0.0, 1.0));
+ }
+ }`
+ });
+
+ // Verify the whole range of the buffer has been initialized to 0 in a compute shader.
+ t.TestBufferZeroInitInBindGroup(computeShaderModule, buffer, bufferOffset, boundBufferSize);
+});
+
+g.test('storage_buffer').
+desc(
+ `Verify when we use a GPUBuffer as a storage buffer just after the creation of that
+ GPUBuffer, all the contents in that GPUBuffer have been initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 256])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+ const boundBufferSize = 16;
+ const buffer = t.device.createBuffer({
+ size: bufferOffset + boundBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+ t.trackForCleanup(buffer);
+
+ const computeShaderModule = t.device.createShaderModule({
+ code: `
+ struct SSBO {
+ value : vec4<u32>
+ };
+ @group(0) @binding(0) var<storage, read_write> ssbo : SSBO;
+ @group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>;
+
+ @compute @workgroup_size(1) fn main() {
+ if (all(ssbo.value == vec4<u32>(0u, 0u, 0u, 0u))) {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(0.0, 1.0, 0.0, 1.0));
+ } else {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(1.0, 0.0, 0.0, 1.0));
+ }
+ }`
+ });
+
+ // Verify the whole range of the buffer has been initialized to 0 in a compute shader.
+ t.TestBufferZeroInitInBindGroup(computeShaderModule, buffer, bufferOffset, boundBufferSize);
+});
+
+g.test('vertex_buffer').
+desc(
+ `Verify when we use a GPUBuffer as a vertex buffer just after the creation of that
+ GPUBuffer, all the contents in that GPUBuffer have been initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 16])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+
+ const renderPipeline = t.CreateRenderPipelineForTest(
+ t.device.createShaderModule({
+ code: `
+ struct VertexOut {
+ @location(0) color : vec4<f32>,
+ @builtin(position) position : vec4<f32>,
+ };
+
+ @vertex fn main(@location(0) pos : vec4<f32>) -> VertexOut {
+ var output : VertexOut;
+ if (all(pos == vec4<f32>(0.0, 0.0, 0.0, 0.0))) {
+ output.color = vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ } else {
+ output.color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }
+ output.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ return output;
+ }`
+ }),
+ true
+ );
+
+ const bufferSize = 16 + bufferOffset;
+ const vertexBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(vertexBuffer);
+
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [1, 1, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.trackForCleanup(outputTexture);
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setVertexBuffer(0, vertexBuffer, bufferOffset);
+ renderPass.setPipeline(renderPipeline);
+ renderPass.draw(1);
+ renderPass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.CheckBufferAndOutputTexture(vertexBuffer, bufferSize, outputTexture);
+});
+
+g.test('index_buffer').
+desc(
+ `Verify when we use a GPUBuffer as an index buffer just after the creation of that
+GPUBuffer, all the contents in that GPUBuffer have been initialized to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 16])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+
+ const renderPipeline = t.CreateRenderPipelineForTest(
+ t.device.createShaderModule({
+ code: `
+ struct VertexOut {
+ @location(0) color : vec4<f32>,
+ @builtin(position) position : vec4<f32>,
+ };
+
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut {
+ var output : VertexOut;
+ if (VertexIndex == 0u) {
+ output.color = vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ } else {
+ output.color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }
+ output.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ return output;
+ }`
+ }),
+ false
+ );
+
+ // The size of GPUBuffer must be at least 4.
+ const bufferSize = 4 + bufferOffset;
+ const indexBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(indexBuffer);
+
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [1, 1, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.trackForCleanup(outputTexture);
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(renderPipeline);
+ renderPass.setIndexBuffer(indexBuffer, 'uint16', bufferOffset, 4);
+ renderPass.drawIndexed(1);
+ renderPass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.CheckBufferAndOutputTexture(indexBuffer, bufferSize, outputTexture);
+});
+
+g.test('indirect_buffer_for_draw_indirect').
+desc(
+ `Verify when we use a GPUBuffer as an indirect buffer for drawIndirect() or
+drawIndexedIndirect() just after the creation of that GPUBuffer, all the contents in that GPUBuffer
+have been initialized to 0.`
+).
+params((u) =>
+u.combine('test_indexed_draw', [true, false]).beginSubcases().combine('bufferOffset', [0, 16])
+).
+fn((t) => {
+ const { test_indexed_draw, bufferOffset } = t.params;
+
+ const renderPipeline = t.CreateRenderPipelineForTest(
+ t.device.createShaderModule({
+ code: `
+ struct VertexOut {
+ @location(0) color : vec4<f32>,
+ @builtin(position) position : vec4<f32>,
+ };
+
+ @vertex fn main() -> VertexOut {
+ var output : VertexOut;
+ output.color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ output.position = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ return output;
+ }`
+ }),
+ false
+ );
+
+ const kDrawIndirectParametersSize = 16;
+ const kDrawIndexedIndirectParametersSize = 20;
+ const bufferSize =
+ Math.max(kDrawIndirectParametersSize, kDrawIndexedIndirectParametersSize) + bufferOffset;
+ const indirectBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.INDIRECT
+ });
+ t.trackForCleanup(indirectBuffer);
+
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [1, 1, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.trackForCleanup(outputTexture);
+
+ // Initialize outputTexture to green.
+ const encoder = t.device.createCommandEncoder();
+ t.RecordInitializeTextureColor(encoder, outputTexture, { r: 0.0, g: 1.0, b: 0.0, a: 1.0 });
+
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(renderPipeline);
+
+ let indexBuffer = undefined;
+ if (test_indexed_draw) {
+ indexBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ });
+ t.trackForCleanup(indexBuffer);
+ renderPass.setIndexBuffer(indexBuffer, 'uint16');
+ renderPass.drawIndexedIndirect(indirectBuffer, bufferOffset);
+ } else {
+ renderPass.drawIndirect(indirectBuffer, bufferOffset);
+ }
+
+ renderPass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // The indirect buffer should be lazily cleared to 0, so we actually draw nothing and the color
+ // attachment will keep its original color (green) after we end the render pass.
+ t.CheckBufferAndOutputTexture(indirectBuffer, bufferSize, outputTexture);
+});
+
+g.test('indirect_buffer_for_dispatch_indirect').
+desc(
+ `Verify when we use a GPUBuffer as an indirect buffer for dispatchWorkgroupsIndirect() just
+ after the creation of that GPUBuffer, all the contents in that GPUBuffer have been initialized
+ to 0.`
+).
+paramsSubcasesOnly((u) => u.combine('bufferOffset', [0, 16])).
+fn((t) => {
+ const { bufferOffset } = t.params;
+
+ const computePipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var outImage : texture_storage_2d<rgba8unorm, write>;
+
+ @compute @workgroup_size(1) fn main() {
+ textureStore(outImage, vec2<i32>(0, 0), vec4<f32>(1.0, 0.0, 0.0, 1.0));
+ }`
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const kDispatchIndirectParametersSize = 12;
+ const bufferSize = kDispatchIndirectParametersSize + bufferOffset;
+ const indirectBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.INDIRECT
+ });
+ t.trackForCleanup(indirectBuffer);
+
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [1, 1, 1],
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.STORAGE_BINDING
+ });
+ t.trackForCleanup(outputTexture);
+
+ // Initialize outputTexture to green.
+ const encoder = t.device.createCommandEncoder();
+ t.RecordInitializeTextureColor(encoder, outputTexture, { r: 0.0, g: 1.0, b: 0.0, a: 1.0 });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: outputTexture.createView()
+ }]
+
+ });
+
+ // The indirect buffer should be lazily cleared to 0, so we actually don't execute the compute
+ // shader and the output texture should keep its original color (green).
+ const computePass = encoder.beginComputePass();
+ computePass.setBindGroup(0, bindGroup);
+ computePass.setPipeline(computePipeline);
+ computePass.dispatchWorkgroupsIndirect(indirectBuffer, bufferOffset);
+ computePass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // The indirect buffer should be lazily cleared to 0, so we actually draw nothing and the color
+ // attachment will keep its original color (green) after we end the compute pass.
+ t.CheckBufferAndOutputTexture(indirectBuffer, bufferSize, outputTexture);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_copy.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_copy.js
new file mode 100644
index 0000000000..4025467306
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_copy.js
@@ -0,0 +1,66 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../../../../common/util/util.js';import { kTextureFormatInfo } from '../../../../format_info.js';import { virtualMipSize } from '../../../../util/texture/base.js';
+
+
+export const checkContentsByBufferCopy = (
+t,
+params,
+texture,
+state,
+subresourceRange) =>
+{
+ for (const { level: mipLevel, layer } of subresourceRange.each()) {
+ assert(params.format in kTextureFormatInfo);
+ const format = params.format;
+
+ t.expectSingleColor(texture, format, {
+ size: [t.textureWidth, t.textureHeight, t.textureDepth],
+ dimension: params.dimension,
+ slice: params.dimension === '2d' ? layer : 0,
+ layout: { mipLevel, aspect: params.aspect },
+ exp: t.stateToTexelComponents[state]
+ });
+ }
+};
+
+export const checkContentsByTextureCopy = (
+t,
+params,
+texture,
+state,
+subresourceRange) =>
+{
+ for (const { level, layer } of subresourceRange.each()) {
+ assert(params.format in kTextureFormatInfo);
+ const format = params.format;
+
+ const [width, height, depth] = virtualMipSize(
+ params.dimension,
+ [t.textureWidth, t.textureHeight, t.textureDepth],
+ level
+ );
+
+ const dst = t.device.createTexture({
+ dimension: params.dimension,
+ size: [width, height, depth],
+ format: params.format,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
+ });
+ t.trackForCleanup(dst);
+
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.copyTextureToTexture(
+ { texture, mipLevel: level, origin: { x: 0, y: 0, z: layer } },
+ { texture: dst, mipLevel: 0 },
+ { width, height, depthOrArrayLayers: depth }
+ );
+ t.queue.submit([commandEncoder.finish()]);
+
+ t.expectSingleColor(dst, format, {
+ size: [width, height, depth],
+ exp: t.stateToTexelComponents[state],
+ layout: { mipLevel: 0, aspect: params.aspect }
+ });
+ }
+}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_ds_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_ds_test.js
new file mode 100644
index 0000000000..3a88f02a6a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_ds_test.js
@@ -0,0 +1,200 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../../../../common/util/util.js';import { kTextureFormatInfo } from '../../../../format_info.js';
+import { virtualMipSize } from '../../../../util/texture/base.js';
+
+
+function makeFullscreenVertexModule(device) {
+ return device.createShaderModule({
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)
+ -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-1.0, -3.0),
+ vec2<f32>( 3.0, 1.0),
+ vec2<f32>(-1.0, 1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+ `
+ });
+}
+
+function getDepthTestEqualPipeline(
+t,
+format,
+sampleCount,
+expected)
+{
+ return t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ entryPoint: 'main',
+ module: makeFullscreenVertexModule(t.device)
+ },
+ fragment: {
+ entryPoint: 'main',
+ module: t.device.createShaderModule({
+ code: `
+ struct Outputs {
+ @builtin(frag_depth) FragDepth : f32,
+ @location(0) outSuccess : f32,
+ };
+
+ @fragment
+ fn main() -> Outputs {
+ var output : Outputs;
+ output.FragDepth = f32(${expected});
+ output.outSuccess = 1.0;
+ return output;
+ }
+ `
+ }),
+ targets: [{ format: 'r8unorm' }]
+ },
+ depthStencil: {
+ format,
+ depthCompare: 'equal',
+ depthWriteEnabled: false
+ },
+ primitive: { topology: 'triangle-list' },
+ multisample: { count: sampleCount }
+ });
+}
+
+function getStencilTestEqualPipeline(
+t,
+format,
+sampleCount)
+{
+ return t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ entryPoint: 'main',
+ module: makeFullscreenVertexModule(t.device)
+ },
+ fragment: {
+ entryPoint: 'main',
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) f32 {
+ return 1.0;
+ }
+ `
+ }),
+ targets: [{ format: 'r8unorm' }]
+ },
+ depthStencil: {
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ format,
+ stencilFront: { compare: 'equal' },
+ stencilBack: { compare: 'equal' }
+ },
+ primitive: { topology: 'triangle-list' },
+ multisample: { count: sampleCount }
+ });
+}
+
+const checkContents = (
+type,
+t,
+params,
+texture,
+state,
+subresourceRange) =>
+{
+ const formatInfo = kTextureFormatInfo[params.format];
+
+ assert(params.dimension === '2d');
+ for (const viewDescriptor of t.generateTextureViewDescriptorsForRendering(
+ 'all',
+ subresourceRange
+ )) {
+ assert(viewDescriptor.baseMipLevel !== undefined);
+ const [width, height] = virtualMipSize(
+ params.dimension,
+ [t.textureWidth, t.textureHeight, 1],
+ viewDescriptor.baseMipLevel
+ );
+
+ const renderTexture = t.device.createTexture({
+ size: [width, height, 1],
+ format: 'r8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ sampleCount: params.sampleCount
+ });
+
+ let resolveTexture = undefined;
+ let resolveTarget = undefined;
+ if (params.sampleCount > 1) {
+ resolveTexture = t.device.createTexture({
+ size: [width, height, 1],
+ format: 'r8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ resolveTarget = resolveTexture.createView();
+ }
+
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.pushDebugGroup('checkContentsWithDepthStencil');
+
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTexture.createView(),
+ resolveTarget,
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'load',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment: {
+ view: texture.createView(viewDescriptor),
+ depthStoreOp: formatInfo.depth ? 'store' : undefined,
+ depthLoadOp: formatInfo.depth ? 'load' : undefined,
+ stencilStoreOp: formatInfo.stencil ? 'store' : undefined,
+ stencilLoadOp: formatInfo.stencil ? 'load' : undefined
+ }
+ });
+
+ switch (type) {
+ case 'depth':{
+ const expectedDepth = t.stateToTexelComponents[state].Depth;
+ assert(expectedDepth !== undefined);
+
+ pass.setPipeline(
+ getDepthTestEqualPipeline(t, params.format, params.sampleCount, expectedDepth)
+ );
+ break;
+ }
+
+ case 'stencil':{
+ const expectedStencil = t.stateToTexelComponents[state].Stencil;
+ assert(expectedStencil !== undefined);
+
+ pass.setPipeline(getStencilTestEqualPipeline(t, params.format, params.sampleCount));
+ pass.setStencilReference(expectedStencil);
+ break;
+ }
+ }
+
+ pass.draw(3);
+ pass.end();
+
+ commandEncoder.popDebugGroup();
+ t.queue.submit([commandEncoder.finish()]);
+
+ t.expectSingleColor(resolveTexture || renderTexture, 'r8unorm', {
+ size: [width, height, 1],
+ exp: { R: 1 }
+ });
+ }
+};
+
+export const checkContentsByDepthTest = (...args) =>
+checkContents('depth', ...args);
+
+export const checkContentsByStencilTest = (...args) =>
+checkContents('stencil', ...args); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_sampling.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_sampling.js
new file mode 100644
index 0000000000..89a4a79e32
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/check_texture/by_sampling.js
@@ -0,0 +1,157 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../../../common/util/util.js';import { kTextureFormatInfo } from '../../../../format_info.js';import { virtualMipSize } from '../../../../util/texture/base.js';
+import {
+ kTexelRepresentationInfo,
+ getSingleDataType,
+ getComponentReadbackTraits } from
+'../../../../util/texture/texel_data.js';
+
+
+export const checkContentsBySampling = (
+t,
+params,
+texture,
+state,
+subresourceRange) =>
+{
+ assert(params.format in kTextureFormatInfo);
+ const format = params.format;
+ const rep = kTexelRepresentationInfo[format];
+
+ for (const { level, layers } of subresourceRange.mipLevels()) {
+ const [width, height, depth] = virtualMipSize(
+ params.dimension,
+ [t.textureWidth, t.textureHeight, t.textureDepth],
+ level
+ );
+
+ const { ReadbackTypedArray, shaderType } = getComponentReadbackTraits(
+ getSingleDataType(format)
+ );
+
+ const componentOrder = rep.componentOrder;
+ const componentCount = componentOrder.length;
+
+ // For single-component textures, generates .r
+ // For multi-component textures, generates ex.)
+ // .rgba[i], .bgra[i], .rgb[i]
+ const indexExpression =
+ componentCount === 1 ?
+ componentOrder[0].toLowerCase() :
+ componentOrder.map((c) => c.toLowerCase()).join('') + '[i]';
+
+ const _xd = '_' + params.dimension;
+ const _multisampled = params.sampleCount > 1 ? '_multisampled' : '';
+ const texelIndexExpression =
+ params.dimension === '2d' ?
+ 'vec2<i32>(GlobalInvocationID.xy)' :
+ params.dimension === '3d' ?
+ 'vec3<i32>(GlobalInvocationID.xyz)' :
+ params.dimension === '1d' ?
+ 'i32(GlobalInvocationID.x)' :
+ unreachable();
+ const computePipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ entryPoint: 'main',
+ module: t.device.createShaderModule({
+ code: `
+ struct Constants {
+ level : i32
+ };
+
+ @group(0) @binding(0) var<uniform> constants : Constants;
+ @group(0) @binding(1) var myTexture : texture${_multisampled}${_xd}<${shaderType}>;
+
+ struct Result {
+ values : array<${shaderType}>
+ };
+ @group(0) @binding(3) var<storage, read_write> result : Result;
+
+ @compute @workgroup_size(1)
+ fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
+ let flatIndex : u32 = ${componentCount}u * (
+ ${width}u * ${height}u * GlobalInvocationID.z +
+ ${width}u * GlobalInvocationID.y +
+ GlobalInvocationID.x
+ );
+ let texel : vec4<${shaderType}> = textureLoad(
+ myTexture, ${texelIndexExpression}, constants.level);
+
+ for (var i : u32 = 0u; i < ${componentCount}u; i = i + 1u) {
+ result.values[flatIndex + i] = texel.${indexExpression};
+ }
+ }`
+ })
+ }
+ });
+
+ for (const layer of layers) {
+ const ubo = t.device.createBuffer({
+ mappedAtCreation: true,
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
+ });
+ new Int32Array(ubo.getMappedRange(), 0, 1)[0] = level;
+ ubo.unmap();
+
+ const byteLength =
+ width * height * depth * ReadbackTypedArray.BYTES_PER_ELEMENT * rep.componentOrder.length;
+ const resultBuffer = t.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(resultBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: ubo }
+ },
+ {
+ binding: 1,
+ resource: texture.createView({
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ dimension: params.dimension
+ })
+ },
+ {
+ binding: 3,
+ resource: {
+ buffer: resultBuffer
+ }
+ }]
+
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginComputePass();
+ pass.setPipeline(computePipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(width, height, depth);
+ pass.end();
+ t.queue.submit([commandEncoder.finish()]);
+ ubo.destroy();
+
+ const expectedValues = new ReadbackTypedArray(new ArrayBuffer(byteLength));
+ const expectedState = t.stateToTexelComponents[state];
+ let i = 0;
+ for (let d = 0; d < depth; ++d) {
+ for (let h = 0; h < height; ++h) {
+ for (let w = 0; w < width; ++w) {
+ for (const c of rep.componentOrder) {
+ const value = expectedState[c];
+ assert(value !== undefined);
+ expectedValues[i++] = value;
+ }
+ }
+ }
+ }
+ t.expectGPUBufferValuesEqual(resultBuffer, expectedValues);
+ }
+ }
+}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/texture_zero.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/texture_zero.spec.js
new file mode 100644
index 0000000000..d7e83601c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/resource_init/texture_zero.spec.js
@@ -0,0 +1,645 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test uninitialized textures are initialized to zero when read.
+
+TODO:
+- test by sampling depth/stencil [1]
+- test by copying out of stencil [2]
+- 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 {
+ kUnitCaseParamsBuilder } 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 } from
+
+
+'../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { virtualMipSize } from '../../../util/texture/base.js';
+import { createTextureUploadBuffer } from '../../../util/texture/layout.js';
+import { SubresourceRange } from '../../../util/texture/subresource.js';
+import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
+
+export let UninitializeMethod = /*#__PURE__*/function (UninitializeMethod) {UninitializeMethod["Creation"] = "Creation";UninitializeMethod["StoreOpClear"] = "StoreOpClear";return UninitializeMethod;}({});
+
+// The texture was rendered to with GPUStoreOp "clear"
+
+const kUninitializeMethods = Object.keys(UninitializeMethod);
+
+export let ReadMethod = /*#__PURE__*/function (ReadMethod) {ReadMethod["Sample"] = "Sample";ReadMethod["CopyToBuffer"] = "CopyToBuffer";ReadMethod["CopyToTexture"] = "CopyToTexture";ReadMethod["DepthTest"] = "DepthTest";ReadMethod["StencilTest"] = "StencilTest";ReadMethod["ColorBlending"] = "ColorBlending";ReadMethod["Storage"] = "Storage";return ReadMethod;}({});
+
+
+
+
+
+
+// Read the texture as a storage texture
+
+
+// Test with these mip level counts
+
+const kMipLevelCounts = [1, 5];
+
+// For each mip level count, define the mip ranges to leave uninitialized.
+const kUninitializedMipRangesToTest = {
+ 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 = [1, 4];
+
+// Test with these layer counts.
+
+
+// For each layer count, define the layers to leave uninitialized.
+const kUninitializedLayerRangesToTest = {
+ 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 let InitializedState = /*#__PURE__*/function (InitializedState) {InitializedState[InitializedState["Canary"] = 0] = "Canary";InitializedState[InitializedState["Zero"] = 1] = "Zero";return InitializedState;}({});
+
+// 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,
+format)
+{
+ 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,
+sampleCount,
+uninitializeMethod,
+readMethod)
+{
+ let usage = 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 {
+
+
+
+ constructor(sharedState, rec, params) {
+ super(sharedState, rec, params);
+ this.p = params;
+
+ const stateToTexelComponents = (state) => {
+ 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() {
+ let width = 1 << this.p.mipLevelCount;
+ if (this.p.nonPowerOfTwo) {
+ width = 2 * width - 1;
+ }
+ return width;
+ }
+
+ get textureHeight() {
+ 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() {
+ return this.p.dimension === '3d' ? 11 : 1;
+ }
+
+ get textureDepthOrArrayLayers() {
+ 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() {
+ 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() {
+ const uninitialized = 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,
+ subresourceRange)
+ {
+ const viewDescriptor = {
+ 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
+ };
+ }
+ }
+
+ initializeWithStoreOp(
+ state,
+ texture,
+ subresourceRange)
+ {
+ 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 = {
+ 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()]);
+ }
+
+ initializeWithCopy(
+ texture,
+ state,
+ subresourceRange)
+ {
+ assert(this.p.format in kTextureFormatInfo);
+ const format = this.p.format;
+
+ 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,
+ state,
+ subresourceRange)
+ {
+ 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, subresourceRange) {
+ 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 = {
+ 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 };
+ yield { layerCount: 7 };
+ break;
+ case '1d':
+ case '3d':
+ yield { layerCount: 1 };
+ 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;
+});
+
+
+
+
+
+
+
+
+
+
+
+import { checkContentsByBufferCopy, checkContentsByTextureCopy } from './check_texture/by_copy.js';
+import {
+ checkContentsByDepthTest,
+ checkContentsByStencilTest } from
+'./check_texture/by_ds_test.js';
+import { checkContentsBySampling } from './check_texture/by_sampling.js';
+
+const checkContentsImpl = {
+ Sample: checkContentsBySampling,
+ CopyToBuffer: checkContentsByBufferCopy,
+ CopyToTexture: checkContentsByTextureCopy,
+ DepthTest: checkContentsByDepthTest,
+ StencilTest: checkContentsByStencilTest,
+ ColorBlending: (t) => t.skip('Not implemented'),
+ Storage: (t) => t.skip('Not implemented')
+};
+
+export const g = makeTestGroup(TextureZeroInitTest);
+
+g.test('uninitialized_texture_is_zero').
+params(kTestParams).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[t.params.format].feature);
+}).
+fn((t) => {
+ const usage = getRequiredTextureUsage(
+ t.params.format,
+ t.params.sampleCount,
+ t.params.uninitializeMethod,
+ t.params.readMethod
+ );
+
+ const texture = t.device.createTexture({
+ size: [t.textureWidth, t.textureHeight, t.textureDepthOrArrayLayers],
+ format: t.params.format,
+ dimension: t.params.dimension,
+ usage,
+ mipLevelCount: t.params.mipLevelCount,
+ sampleCount: t.params.sampleCount
+ });
+ t.trackForCleanup(texture);
+
+ if (t.params.canaryOnCreation) {
+ // Initialize some subresources with canary values
+ for (const subresourceRange of t.iterateInitializedSubresources()) {
+ t.initializeTexture(texture, InitializedState.Canary, subresourceRange);
+ }
+ }
+
+ switch (t.params.uninitializeMethod) {
+ case UninitializeMethod.Creation:
+ break;
+ case UninitializeMethod.StoreOpClear:
+ // Initialize the rest of the resources.
+ for (const subresourceRange of t.iterateUninitializedSubresources()) {
+ t.initializeTexture(texture, InitializedState.Canary, subresourceRange);
+ }
+ // Then use a store op to discard their contents.
+ for (const subresourceRange of t.iterateUninitializedSubresources()) {
+ t.discardTexture(texture, subresourceRange);
+ }
+ break;
+ default:
+ unreachable();
+ }
+
+ // Check that all uninitialized resources are zero.
+ for (const subresourceRange of t.iterateUninitializedSubresources()) {
+ checkContentsImpl[t.params.readMethod](
+ t,
+ t.params,
+ texture,
+ InitializedState.Zero,
+ subresourceRange
+ );
+ }
+
+ if (t.params.canaryOnCreation) {
+ // Check the all other resources are unchanged.
+ for (const subresourceRange of t.iterateInitializedSubresources()) {
+ checkContentsImpl[t.params.readMethod](
+ t,
+ t.params,
+ texture,
+ InitializedState.Canary,
+ subresourceRange
+ );
+ }
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/anisotropy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/anisotropy.spec.js
new file mode 100644
index 0000000000..8408f619f4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/anisotropy.spec.js
@@ -0,0 +1,325 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests the behavior of anisotropic filtering.
+
+TODO:
+Note that anisotropic filtering is never guaranteed to occur, but we might be able to test some
+things. If there are no guarantees we can issue warnings instead of failures. Ideas:
+ - No *more* than the provided maxAnisotropy samples are used, by testing how many unique
+ sample values come out of the sample operation.
+ - Check anisotropy is done in the correct direction (by having a 2D gradient and checking we get
+ more of the color in the correct direction).
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+
+const kRTSize = 16;
+const kBytesPerRow = 256;
+const xMiddle = kRTSize / 2; // we check the pixel value in the middle of the render target
+const kColorAttachmentFormat = 'rgba8unorm';
+const kTextureFormat = 'rgba8unorm';
+const colors = [
+new Uint8Array([0xff, 0x00, 0x00, 0xff]), // miplevel = 0
+new Uint8Array([0x00, 0xff, 0x00, 0xff]), // miplevel = 1
+new Uint8Array([0x00, 0x00, 0xff, 0xff]) // miplevel = 2
+];
+const checkerColors = [
+new Uint8Array([0xff, 0x00, 0x00, 0xff]),
+new Uint8Array([0x00, 0xff, 0x00, 0xff])];
+
+
+// renders texture a slanted plane placed in a specific way
+class SamplerAnisotropicFilteringSlantedPlaneTest extends GPUTest {
+ copyRenderTargetToBuffer(rt) {
+ const byteLength = kRTSize * kBytesPerRow;
+ const buffer = this.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyTextureToBuffer(
+ { texture: rt, mipLevel: 0, origin: [0, 0, 0] },
+ { buffer, bytesPerRow: kBytesPerRow, rowsPerImage: kRTSize },
+ { width: kRTSize, height: kRTSize, depthOrArrayLayers: 1 }
+ );
+ this.queue.submit([commandEncoder.finish()]);
+
+ return buffer;
+ }
+
+
+ async init() {
+ await super.init();
+
+ this.pipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ struct Outputs {
+ @builtin(position) Position : vec4<f32>,
+ @location(0) fragUV : vec2<f32>,
+ };
+
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32) -> Outputs {
+ var position : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
+ vec3<f32>(-0.5, 0.5, -0.5),
+ vec3<f32>(0.5, 0.5, -0.5),
+ vec3<f32>(-0.5, 0.5, 0.5),
+ vec3<f32>(-0.5, 0.5, 0.5),
+ vec3<f32>(0.5, 0.5, -0.5),
+ vec3<f32>(0.5, 0.5, 0.5));
+ // uv is pre-scaled to mimic repeating tiled texture
+ var uv : array<vec2<f32>, 6> = array<vec2<f32>, 6>(
+ vec2<f32>(0.0, 0.0),
+ vec2<f32>(1.0, 0.0),
+ vec2<f32>(0.0, 50.0),
+ vec2<f32>(0.0, 50.0),
+ vec2<f32>(1.0, 0.0),
+ vec2<f32>(1.0, 50.0));
+ // draw a slanted plane in a specific way
+ let matrix : mat4x4<f32> = mat4x4<f32>(
+ vec4<f32>(-1.7320507764816284, 1.8322050568049563e-16, -6.176817699518044e-17, -6.170640314703498e-17),
+ vec4<f32>(-2.1211504944260596e-16, -1.496108889579773, 0.5043753981590271, 0.5038710236549377),
+ vec4<f32>(0.0, -43.63650894165039, -43.232173919677734, -43.18894577026367),
+ vec4<f32>(0.0, 21.693578720092773, 21.789791107177734, 21.86800193786621));
+
+ var output : Outputs;
+ output.fragUV = uv[VertexIndex];
+ output.Position = matrix * vec4<f32>(position[VertexIndex], 1.0);
+ return output;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var sampler0 : sampler;
+ @group(0) @binding(1) var texture0 : texture_2d<f32>;
+
+ @fragment fn main(
+ @builtin(position) FragCoord : vec4<f32>,
+ @location(0) fragUV: vec2<f32>)
+ -> @location(0) vec4<f32> {
+ return textureSample(texture0, sampler0, fragUV);
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+ }
+
+ // return the render target texture object
+ drawSlantedPlane(textureView, sampler) {
+ // make sure it's already initialized
+ assert(this.pipeline !== undefined);
+
+ const bindGroup = this.device.createBindGroup({
+ entries: [
+ { binding: 0, resource: sampler },
+ { binding: 1, resource: textureView }],
+
+ layout: this.pipeline.getBindGroupLayout(0)
+ });
+
+ const colorAttachment = this.device.createTexture({
+ format: kColorAttachmentFormat,
+ size: { width: kRTSize, height: kRTSize, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ storeOp: 'store',
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(this.pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.draw(6);
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ return colorAttachment;
+ }
+}
+
+export const g = makeTestGroup(TextureTestMixin(SamplerAnisotropicFilteringSlantedPlaneTest));
+
+g.test('anisotropic_filter_checkerboard').
+desc(
+ `Anisotropic filter rendering tests that draws a slanted plane and samples from a texture
+ that only has a top level mipmap, the content of which is like a checkerboard.
+ We will check the rendering result using sampler with maxAnisotropy values to be
+ different from each other, as the sampling rate is different.
+ We will also check if those large maxAnisotropy values are clamped so that rendering is the
+ same as the supported upper limit say 16.
+ A similar webgl demo is at https://jsfiddle.net/yqnbez24`
+).
+fn(async (t) => {
+ // init texture with only a top level mipmap
+ const textureSize = 32;
+ const texture = t.device.createTexture({
+ mipLevelCount: 1,
+ size: { width: textureSize, height: textureSize, depthOrArrayLayers: 1 },
+ format: kTextureFormat,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ const textureEncoder = t.device.createCommandEncoder();
+
+ const bufferSize = kBytesPerRow * textureSize; // RGBA8 for each pixel (256 > 16 * 4)
+
+ // init checkerboard texture data
+ const data = new Uint8Array(bufferSize);
+ for (let r = 0; r < textureSize; r++) {
+ const o = r * kBytesPerRow;
+ for (let c = o, end = o + textureSize * 4; c < end; c += 4) {
+ const cid = (r + (c - o) / 4) % 2;
+ const color = checkerColors[cid];
+ data[c] = color[0];
+ data[c + 1] = color[1];
+ data[c + 2] = color[2];
+ data[c + 3] = color[3];
+ }
+ }
+ const buffer = t.makeBufferWithContents(
+ data,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ const bytesPerRow = kBytesPerRow;
+ const rowsPerImage = textureSize;
+
+ textureEncoder.copyBufferToTexture(
+ {
+ buffer,
+ bytesPerRow,
+ rowsPerImage
+ },
+ {
+ texture,
+ mipLevel: 0,
+ origin: [0, 0, 0]
+ },
+ [textureSize, textureSize, 1]
+ );
+
+ t.device.queue.submit([textureEncoder.finish()]);
+
+ const textureView = texture.createView();
+ const byteLength = kRTSize * kBytesPerRow;
+ const results = [];
+
+ for (const maxAnisotropy of [1, 16, 1024]) {
+ const sampler = t.device.createSampler({
+ magFilter: 'linear',
+ minFilter: 'linear',
+ mipmapFilter: 'linear',
+ maxAnisotropy
+ });
+ const result = await t.readGPUBufferRangeTyped(
+ t.copyRenderTargetToBuffer(t.drawSlantedPlane(textureView, sampler)),
+ { type: Uint8Array, typedLength: byteLength }
+ );
+ results.push(result);
+ }
+
+ const check0 = checkElementsEqual(results[0].data, results[1].data);
+ if (check0 === undefined) {
+ t.warn('Render results with sampler.maxAnisotropy being 1 and 16 should be different.');
+ }
+ const check1 = checkElementsEqual(results[1].data, results[2].data);
+ if (check1 !== undefined) {
+ t.expect(
+ false,
+ 'Render results with sampler.maxAnisotropy being 16 and 1024 should be the same.'
+ );
+ }
+
+ for (const result of results) {
+ result.cleanup();
+ }
+});
+
+g.test('anisotropic_filter_mipmap_color').
+desc(
+ `Anisotropic filter rendering tests that draws a slanted plane and samples from a texture
+ containing mipmaps of different colors. Given the same fragment with dFdx and dFdy for uv being different,
+ sampler with bigger maxAnisotropy value tends to bigger mip levels to provide better details.
+ We can then look at the color of the fragment to know which mip level is being sampled from and to see
+ if it fits expectations.
+ A similar webgl demo is at https://jsfiddle.net/t8k7c95o/5/`
+).
+paramsSimple([
+{
+ maxAnisotropy: 1,
+ _results: [
+ { coord: { x: xMiddle, y: 2 }, expected: colors[2] },
+ { coord: { x: xMiddle, y: 6 }, expected: [colors[0], colors[1]] }],
+
+ _generateWarningOnly: false
+},
+{
+ maxAnisotropy: 4,
+ _results: [
+ { coord: { x: xMiddle, y: 2 }, expected: [colors[0], colors[1]] },
+ { coord: { x: xMiddle, y: 6 }, expected: colors[0] }],
+
+ _generateWarningOnly: true
+}]
+).
+fn((t) => {
+ const texture = t.createTextureFromTexelViewsMultipleMipmaps(
+ colors.map((value) => TexelView.fromTexelsAsBytes(kTextureFormat, (_coords) => value)),
+ { size: [4, 4, 1], usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING }
+ );
+ const textureView = texture.createView();
+
+ const sampler = t.device.createSampler({
+ magFilter: 'linear',
+ minFilter: 'linear',
+ mipmapFilter: 'linear',
+ maxAnisotropy: t.params.maxAnisotropy
+ });
+
+ const colorAttachment = t.drawSlantedPlane(textureView, sampler);
+
+ const pixelComparisons = [];
+ for (const entry of t.params._results) {
+ if (entry.expected instanceof Uint8Array) {
+ // equal exactly one color
+ pixelComparisons.push({ coord: entry.coord, exp: entry.expected });
+ } else {
+ // a lerp between two colors
+ // MAINTENANCE_TODO: Unify comparison to allow for a strict in-between comparison to support
+ // this kind of expectation.
+ t.expectSinglePixelBetweenTwoValuesIn2DTexture(
+ colorAttachment,
+ kColorAttachmentFormat,
+ entry.coord,
+ {
+ exp: entry.expected,
+ generateWarningOnly: t.params._generateWarningOnly
+ }
+ );
+ }
+ }
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, pixelComparisons);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/filter_mode.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/filter_mode.spec.js
new file mode 100644
index 0000000000..860eb03f8d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/filter_mode.spec.js
@@ -0,0 +1,1143 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests the behavior of different filtering modes in minFilter/magFilter/mipmapFilter.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kAddressModes, kMipmapFilterModes } from '../../../capability_info.js';
+import {
+
+ kRenderableColorTextureFormats,
+ kTextureFormatInfo } from
+'../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { getTextureCopyLayout } from '../../../util/texture/layout.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+// Simple checkerboard 2x2 texture used as a base for the sampling.
+const kCheckerTextureSize = 2;
+const kCheckerTextureData = [
+{ R: 1.0, G: 1.0, B: 1.0, A: 1.0 },
+{ R: 0.0, G: 0.0, B: 0.0, A: 1.0 },
+{ R: 0.0, G: 0.0, B: 0.0, A: 1.0 },
+{ R: 1.0, G: 1.0, B: 1.0, A: 1.0 }];
+
+
+class FilterModeTest extends TextureTestMixin(GPUTest) {
+ runFilterRenderPipeline(
+ sampler,
+ module,
+ format,
+ renderSize,
+ vertexCount,
+ instanceCount)
+ {
+ const sampleTexture = this.createTextureFromTexelView(
+ TexelView.fromTexelsAsColors(format, (coord) => {
+ const id = coord.x + coord.y * kCheckerTextureSize;
+ return kCheckerTextureData[id];
+ }),
+ {
+ size: [kCheckerTextureSize, kCheckerTextureSize],
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
+ }
+ );
+ const renderTexture = this.device.createTexture({
+ format,
+ size: renderSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ const pipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs_main'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs_main',
+ targets: [{ format }]
+ }
+ });
+ const bindgroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: sampler },
+ { binding: 1, resource: sampleTexture.createView() }]
+
+ });
+ const commandEncoder = this.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTexture.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(0, bindgroup);
+ renderPass.draw(vertexCount, instanceCount);
+ renderPass.end();
+ this.device.queue.submit([commandEncoder.finish()]);
+ return renderTexture;
+ }
+}
+
+export const g = makeTestGroup(FilterModeTest);
+
+
+
+/* For filter mode 'nearest', we need to check a 6x6 of pixels because 4x4s are identical when using
+ * address mode 'clamp-to-edge' and 'mirror-repeat'. The minFilter and magFilter tests are setup so
+ * that they both render the same results. (See the respective test for details.) The following
+ * table shows the expected results:
+ * u
+ *
+ * repeat clamp-to-edge mirror-repeat
+ *
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * repeat │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ *
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * v clamp-to-edge │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ *
+ * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+ * mirror-repeat │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │ │█│ │█│ │█│ │ │ │ │█│█│█│ │█│ │ │█│█│ │
+ * │█│ │█│ │█│ │ │█│█│█│ │ │ │ │ │█│█│ │ │█│
+*/
+const kNearestRenderSize = 6;
+const kNearestRenderDim = [kNearestRenderSize, kNearestRenderSize];
+const kNearestURepeatVRepeat = [
+[1, 0, 1, 0, 1, 0],
+[0, 1, 0, 1, 0, 1],
+[1, 0, 1, 0, 1, 0],
+[0, 1, 0, 1, 0, 1],
+[1, 0, 1, 0, 1, 0],
+[0, 1, 0, 1, 0, 1]];
+
+const kNearestURepeatVClamped = [
+[1, 0, 1, 0, 1, 0],
+[1, 0, 1, 0, 1, 0],
+[1, 0, 1, 0, 1, 0],
+[0, 1, 0, 1, 0, 1],
+[0, 1, 0, 1, 0, 1],
+[0, 1, 0, 1, 0, 1]];
+
+const kNearestURepeatVMirror = [
+[0, 1, 0, 1, 0, 1],
+[1, 0, 1, 0, 1, 0],
+[1, 0, 1, 0, 1, 0],
+[0, 1, 0, 1, 0, 1],
+[0, 1, 0, 1, 0, 1],
+[1, 0, 1, 0, 1, 0]];
+
+const kNearestUClampedVRepeat = [
+[1, 1, 1, 0, 0, 0],
+[0, 0, 0, 1, 1, 1],
+[1, 1, 1, 0, 0, 0],
+[0, 0, 0, 1, 1, 1],
+[1, 1, 1, 0, 0, 0],
+[0, 0, 0, 1, 1, 1]];
+
+const kNearestUClampedVClamped = [
+[1, 1, 1, 0, 0, 0],
+[1, 1, 1, 0, 0, 0],
+[1, 1, 1, 0, 0, 0],
+[0, 0, 0, 1, 1, 1],
+[0, 0, 0, 1, 1, 1],
+[0, 0, 0, 1, 1, 1]];
+
+const kNearestUClampedVMirror = [
+[0, 0, 0, 1, 1, 1],
+[1, 1, 1, 0, 0, 0],
+[1, 1, 1, 0, 0, 0],
+[0, 0, 0, 1, 1, 1],
+[0, 0, 0, 1, 1, 1],
+[1, 1, 1, 0, 0, 0]];
+
+const kNearestUMirrorVRepeat = [
+[0, 1, 1, 0, 0, 1],
+[1, 0, 0, 1, 1, 0],
+[0, 1, 1, 0, 0, 1],
+[1, 0, 0, 1, 1, 0],
+[0, 1, 1, 0, 0, 1],
+[1, 0, 0, 1, 1, 0]];
+
+const kNearestUMirrorVClamped = [
+[0, 1, 1, 0, 0, 1],
+[0, 1, 1, 0, 0, 1],
+[0, 1, 1, 0, 0, 1],
+[1, 0, 0, 1, 1, 0],
+[1, 0, 0, 1, 1, 0],
+[1, 0, 0, 1, 1, 0]];
+
+const kNearestUMirrorVMirror = [
+[1, 0, 0, 1, 1, 0],
+[0, 1, 1, 0, 0, 1],
+[0, 1, 1, 0, 0, 1],
+[1, 0, 0, 1, 1, 0],
+[1, 0, 0, 1, 1, 0],
+[0, 1, 1, 0, 0, 1]];
+
+
+/* For filter mode 'linear', the tests samples 16 points (to create a 4x4) on what the effective 8x8
+ * expanded texture via the address modes looks like (see table below for what those look like). The
+ * sample points are selected such that no combination of address modes result in the same render.
+ * There is exactly one sample point in each sub 2x2 of the 8x8 texture, thereby yielding the 4x4
+ * result. Note that sampling from the 8x8 texture instead of the 6x6 texture is necessary because
+ * that allows us to keep the results in powers of 2 to minimize floating point errors on different
+ * backends.
+ *
+ * The 8x8 effective textures:
+ * u
+ *
+ * repeat clamp-to-edge mirror-repeat
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * repeat │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ *
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * v clamp-to-edge │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ *
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * mirror-repeat │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │█│ │█│ │█│ │█│ │ │ │ │ │ │█│█│█│█│ │█│█│ │ │█│█│ │ │
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ * │ │█│ │█│ │█│ │█│ │█│█│█│█│ │ │ │ │ │ │ │█│█│ │ │█│█│
+ *
+ *
+ * Sample points:
+ * The sample points are always at a 25% corner of a pixel such that the contributions come from
+ * the 2x2 (doubly outlined) with ratios 1/16, 3/16, or 9/16.
+ * ╔══╤══╦══╤══╦══╤══╦══╤══╗
+ * ║ │ ║ │ ║ │ ║ │ ║
+ * ╟──┼──╫──┼──╫──┼──╫──┼──╢
+ * ║ │▘ ║ ▝│ ║ │▘ ║ ▝│ ║
+ * ╠══╪══╬══╪══╬══╪══╬══╪══╣
+ * ║ │ ║ │ ║ │ ║ │ ║
+ * ╟──┼──╫──┼──╫──┼──╫──┼──╢
+ * ║ │▘ ║ ▝│ ║ │▘ ║ ▝│ ║
+ * ╠══╪══╬══╪══╬══╪══╬══╪══╣
+ * ║ │▖ ║ ▗│ ║ │▖ ║ ▗│ ║
+ * ╟──┼──╫──┼──╫──┼──╫──┼──╢
+ * ║ │ ║ │ ║ │ ║ │ ║
+ * ╠══╪══╬══╪══╬══╪══╬══╪══╣
+ * ║ │▖ ║ ▗│ ║ │▖ ║ ▗│ ║
+ * ╟──┼──╫──┼──╫──┼──╫──┼──╢
+ * ║ │ ║ │ ║ │ ║ │ ║
+ * ╚══╧══╩══╧══╩══╧══╩══╧══╝
+ */
+const kLinearRenderSize = 4;
+const kLinearRenderDim = [kLinearRenderSize, kLinearRenderSize];
+const kLinearURepeatVRepeat = [
+[10, 6, 10, 6],
+[10, 6, 10, 6],
+[6, 10, 6, 10],
+[6, 10, 6, 10]];
+
+const kLinearURepeatVClamped = [
+[12, 4, 12, 4],
+[12, 4, 12, 4],
+[4, 12, 4, 12],
+[4, 12, 4, 12]];
+
+const kLinearURepeatVMirror = [
+[4, 12, 4, 12],
+[12, 4, 12, 4],
+[4, 12, 4, 12],
+[12, 4, 12, 4]];
+
+const kLinearUClampedVRepeat = [
+[12, 12, 4, 4],
+[12, 12, 4, 4],
+[4, 4, 12, 12],
+[4, 4, 12, 12]];
+
+const kLinearUClampedVClamped = [
+[16, 16, 0, 0],
+[16, 16, 0, 0],
+[0, 0, 16, 16],
+[0, 0, 16, 16]];
+
+const kLinearUClampedVMirror = [
+[0, 0, 16, 16],
+[16, 16, 0, 0],
+[0, 0, 16, 16],
+[16, 16, 0, 0]];
+
+const kLinearUMirrorVRepeat = [
+[4, 12, 4, 12],
+[4, 12, 4, 12],
+[12, 4, 12, 4],
+[12, 4, 12, 4]];
+
+const kLinearUMirrorVClamped = [
+[0, 16, 0, 16],
+[0, 16, 0, 16],
+[16, 0, 16, 0],
+[16, 0, 16, 0]];
+
+const kLinearUMirrorVMirror = [
+[16, 0, 16, 0],
+[0, 16, 0, 16],
+[16, 0, 16, 0],
+[0, 16, 0, 16]];
+
+
+
+
+function expectedNearestColors(
+format,
+addressModeU,
+addressModeV)
+{
+ let expectedColors;
+ switch (addressModeU) {
+ case 'clamp-to-edge':{
+ switch (addressModeV) {
+ case 'clamp-to-edge':
+ expectedColors = kNearestUClampedVClamped;
+ break;
+ case 'repeat':
+ expectedColors = kNearestUClampedVRepeat;
+ break;
+ case 'mirror-repeat':
+ expectedColors = kNearestUClampedVMirror;
+ break;
+ }
+ break;
+ }
+ case 'repeat':
+ switch (addressModeV) {
+ case 'clamp-to-edge':
+ expectedColors = kNearestURepeatVClamped;
+ break;
+ case 'repeat':
+ expectedColors = kNearestURepeatVRepeat;
+ break;
+ case 'mirror-repeat':
+ expectedColors = kNearestURepeatVMirror;
+ break;
+ }
+ break;
+ case 'mirror-repeat':
+ switch (addressModeV) {
+ case 'clamp-to-edge':
+ expectedColors = kNearestUMirrorVClamped;
+ break;
+ case 'repeat':
+ expectedColors = kNearestUMirrorVRepeat;
+ break;
+ case 'mirror-repeat':
+ expectedColors = kNearestUMirrorVMirror;
+ break;
+ }
+ break;
+ }
+ return TexelView.fromTexelsAsColors(format, (coord) => {
+ const c = expectedColors[coord.y][coord.x];
+ return { R: c, G: c, B: c, A: 1.0 };
+ });
+}
+function expectedLinearColors(
+format,
+addressModeU,
+addressModeV)
+{
+ let expectedColors;
+ switch (addressModeU) {
+ case 'clamp-to-edge':{
+ switch (addressModeV) {
+ case 'clamp-to-edge':
+ expectedColors = kLinearUClampedVClamped;
+ break;
+ case 'repeat':
+ expectedColors = kLinearUClampedVRepeat;
+ break;
+ case 'mirror-repeat':
+ expectedColors = kLinearUClampedVMirror;
+ break;
+ }
+ break;
+ }
+ case 'repeat':
+ switch (addressModeV) {
+ case 'clamp-to-edge':
+ expectedColors = kLinearURepeatVClamped;
+ break;
+ case 'repeat':
+ expectedColors = kLinearURepeatVRepeat;
+ break;
+ case 'mirror-repeat':
+ expectedColors = kLinearURepeatVMirror;
+ break;
+ }
+ break;
+ case 'mirror-repeat':
+ switch (addressModeV) {
+ case 'clamp-to-edge':
+ expectedColors = kLinearUMirrorVClamped;
+ break;
+ case 'repeat':
+ expectedColors = kLinearUMirrorVRepeat;
+ break;
+ case 'mirror-repeat':
+ expectedColors = kLinearUMirrorVMirror;
+ break;
+ }
+ break;
+ }
+ return TexelView.fromTexelsAsColors(format, (coord) => {
+ const c = expectedColors[coord.y][coord.x];
+ return { R: c / 16, G: c / 16, B: c / 16, A: 1.0 };
+ });
+}
+function expectedColors(
+format,
+filterMode,
+addressModeU,
+addressModeV)
+{
+ switch (filterMode) {
+ case 'nearest':
+ return expectedNearestColors(format, addressModeU, addressModeV);
+ case 'linear':
+ return expectedLinearColors(format, addressModeU, addressModeV);
+ }
+}
+
+/* For the magFilter tests, each rendered pixel is an instanced quad such that the center of the
+ * quad coincides with the center of the pixel. The uv coordinates for each quad are shifted
+ * according to the test so that the center of the quad is at the point we want to sample.
+ *
+ * For the grid offset logic, see this codelab for reference:
+ * https://codelabs.developers.google.com/your-first-webgpu-app#4
+ */
+
+/* The following diagram shows the UV shift (almost to scale) for what the pixel at cell (0,0) looks
+ * like w.r.t the UV of the texture if we just mapped the entire 2x2 texture to the quad. Note that
+ * the square representing the mapped location on the bottom left is actually slighly smaller than a
+ * pixel in order to ensure that we are magnifying the texture and hence using the magFilter. It
+ * should be fairly straightforwards to derive that for each pixel, we are shifting (.5, -.5) from
+ * the picture.
+ *
+ * ┌─┬─┬─┬─┬─┬─┐
+ * ├─┼─┼─┼─┼─┼─┤ (0,0) (1,0)
+ * ├─┼─╔═╪═╗─┼─┤ ╔═══╗
+ * ├─┼─╫─┼─╫─┼─┤ ║─┼─║
+ * ├─┼─╚═╪═╝─┼─┤ ╚═══╝ (-.875,1.625) (-.625,1.625)
+ * ╔═╗─┼─┼─┼─┼─┤ (0,1) (1,1) ╔═╗
+ * ╚═╝─┴─┴─┴─┴─┘ ╚═╝
+ * (-.875,1.875) (-.625,1.875)
+ */
+g.test('magFilter,nearest').
+desc(
+ `
+ Test that for filterable formats, magFilter 'nearest' mode correctly modifies the sampling.
+ - format= {<filterable formats>}
+ - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+filter((t) => {
+ return (
+ kTextureFormatInfo[t.format].color.type === 'float' ||
+ kTextureFormatInfo[t.format].color.type === 'unfilterable-float');
+
+}).
+beginSubcases().
+combine('addressModeU', kAddressModes).
+combine('addressModeV', kAddressModes)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+}).
+fn((t) => {
+ const { format, addressModeU, addressModeV } = t.params;
+ const sampler = t.device.createSampler({
+ addressModeU,
+ addressModeV,
+ magFilter: 'nearest'
+ });
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+
+ struct VertexOut {
+ @builtin(position) pos: vec4f,
+ @location(0) uv: vec2f,
+ };
+
+ @vertex
+ fn vs_main(@builtin(vertex_index) vi : u32,
+ @builtin(instance_index) ii: u32) -> VertexOut {
+ const grid = vec2f(${kNearestRenderSize}, ${kNearestRenderSize});
+ const posBases = array(
+ vec2f(1, 1), vec2f(1, -1), vec2f(-1, -1),
+ vec2f(1, 1), vec2f(-1, -1), vec2f(-1, 1),
+ );
+ const uvBases = array(
+ vec2f(1., 0.), vec2f(1., 1.), vec2f(0., 1.),
+ vec2f(1., 0.), vec2f(0., 1.), vec2f(0., 0.),
+ );
+
+ // Compute the offset of instance plane.
+ let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y));
+ let cellOffset = cell / grid * 2;
+ let pos = (posBases[vi] + 1) / grid - 1 + cellOffset;
+
+ // Compute the offset of the UVs.
+ let uvBase = uvBases[vi] * 0.25 + vec2f(-0.875, 1.625);
+ const uvPerPixelOffset = vec2f(0.5, -0.5);
+ return VertexOut(vec4f(pos, 0.0, 1.0), uvBase + uvPerPixelOffset * cell);
+ }
+
+ @fragment
+ fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {
+ return textureSample(t, s, uv);
+ }
+ `
+ });
+ const vertexCount = 6;
+ const instanceCount = kNearestRenderDim.reduce((sink, current) => sink * current);
+ const render = t.runFilterRenderPipeline(
+ sampler,
+ module,
+ format,
+ kNearestRenderDim,
+ vertexCount,
+ instanceCount
+ );
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: render },
+ expectedColors(format, 'nearest', addressModeU, addressModeV),
+ kNearestRenderDim
+ );
+});
+
+/* The following diagram shows the UV shift (almost to scale) for what the pixel at cell (0,0) (the
+ * dark square) looks like w.r.t the UV of the texture if we just mapped the entire 2x2 texture to
+ * the quad. The other small squares represent the other locations that we are sampling the texture
+ * at. The offsets are defined in the shader.
+ *
+ * ┌────┬────┬────┬────┬────┬────┬────┬────┐
+ * │ │ │ │ │ │ │ │ │
+ * │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤
+ * │ │□ │ □│ │ │□ │ □│ │
+ * │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤
+ * │ │ │ │ │ │ │ │ │
+ * │ │ │ │ │ │ │ │ │ (0,0) (1,0)
+ * ├────┼────┼────╔════╪════╗────┼────┼────┤ ╔═════════╗
+ * │ │□ │ □║ │ ║□ │ □│ │ ║ │ ║
+ * │ │ │ ║ │ ║ │ │ │ ║ │ ║
+ * ├────┼────┼────╫────┼────╫────┼────┼────┤ ║────┼────║
+ * │ │ │ ║ │ ║ │ │ │ ║ │ ║
+ * │ │□ │ □║ │ ║□ │ □│ │ ║ │ ║
+ * ├────┼────┼────╚════╪════╝────┼────┼────┤ ╚═════════╝
+ * │ │ │ │ │ │ │ │ │ (0,1) (1,1)
+ * │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤
+ * │ │ │ │ │ │ │ │ │ (-1,1.75) (-.75,1.75)
+ * │ │■ │ □│ │ │□ │ □│ │ ■
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤ (-1,2) (-.75,2)
+ * │ │ │ │ │ │ │ │ │
+ * │ │ │ │ │ │ │ │ │
+ * └────┴────┴────┴────┴────┴────┴────┴────┘
+ */
+g.test('magFilter,linear').
+desc(
+ `
+ Test that for filterable formats, magFilter 'linear' mode correctly modifies the sampling.
+ - format= {<filterable formats>}
+ - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+filter((t) => {
+ return (
+ kTextureFormatInfo[t.format].color.type === 'float' ||
+ kTextureFormatInfo[t.format].color.type === 'unfilterable-float');
+
+}).
+beginSubcases().
+combine('addressModeU', kAddressModes).
+combine('addressModeV', kAddressModes)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+}).
+fn((t) => {
+ const { format, addressModeU, addressModeV } = t.params;
+ const sampler = t.device.createSampler({
+ addressModeU,
+ addressModeV,
+ magFilter: 'linear'
+ });
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+
+ struct VertexOut {
+ @builtin(position) pos: vec4f,
+ @location(0) uv: vec2f,
+ };
+
+ @vertex
+ fn vs_main(@builtin(vertex_index) vi : u32,
+ @builtin(instance_index) ii: u32) -> VertexOut {
+ const grid = vec2f(${kLinearRenderSize}, ${kLinearRenderSize});
+ const posBases = array(
+ vec2f(1, 1), vec2f(1, -1), vec2f(-1, -1),
+ vec2f(1, 1), vec2f(-1, -1), vec2f(-1, 1),
+ );
+ const uvBases = array(
+ vec2f(1., 0.), vec2f(1., 1.), vec2f(0., 1.),
+ vec2f(1., 0.), vec2f(0., 1.), vec2f(0., 0.),
+ );
+
+ // Compute the offset of instance plane.
+ let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y));
+ let cellOffset = cell / grid * 2;
+ let pos = (posBases[vi] + 1) / grid - 1 + cellOffset;
+
+ // Compute the offset of the UVs.
+ const uOffsets = array(0., 0.75, 2., 2.75);
+ const vOffsets = array(0., 1., 1.75, 2.75);
+ let uvBase = uvBases[vi] * 0.25 + vec2f(-1., 1.75);
+ let uvPixelOffset = vec2f(uOffsets[u32(cell.x)], -vOffsets[u32(cell.y)]);
+ return VertexOut(vec4f(pos, 0.0, 1.0), uvBase + uvPixelOffset);
+ }
+
+ @fragment
+ fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {
+ return textureSample(t, s, uv);
+ }
+ `
+ });
+ const vertexCount = 6;
+ const instanceCount = kLinearRenderDim.reduce((sink, current) => sink * current);
+ const render = t.runFilterRenderPipeline(
+ sampler,
+ module,
+ format,
+ kLinearRenderDim,
+ vertexCount,
+ instanceCount
+ );
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: render },
+ expectedColors(format, 'linear', addressModeU, addressModeV),
+ kLinearRenderDim
+ );
+});
+
+/* For the minFilter tests, each rendered pixel is a small instanced quad that is UV mapped such
+ * that it is either the 6x6 or 8x8 textures from above. Each quad in each cell is then offsetted
+ * and scaled so that the target sample point coincides with the center of the pixel and the texture
+ * is significantly smaller than the pixel to force minFilter mode.
+ *
+ * For the grid offset logic, see this codelab for reference:
+ * https://codelabs.developers.google.com/your-first-webgpu-app#4
+ */
+
+/* The following diagram depicts a single pixel and the sub-pixel sized 6x6 textured quad. The
+ * distances shown in the diagram are pre-grid transformation and relative to the quad. Notice that
+ * for cell (0,0) marked with an x, we need to offset the center by (5/12,5/12), and per cell, the
+ * offset is (-1/6, -1/6).
+ *
+ *
+ * ┌───────────────────────────────────────────────┐
+ * │ │
+ * │ │
+ * │ │
+ * │ │
+ * │ │
+ * │ ┌───┬───┬───┬───┬───┬───┐ │
+ * │ │ │ │ │ │ │ │ │
+ * │ ├───┼───┼───┼───┼───┼───┤ │
+ * │ │ │ │ │ │ │ │ │
+ * │ ├───┼───┼───┼───┼───┼───┤ │
+ * │ │ │ │ │ │ │ │ │
+ * │ ├───┼───┼───x───┼───┼───┤ │ ┐
+ * │ │ │ │ │ │ │ │ │ │
+ * │ ├───┼───┼───┼───┼───┼───┤ │ │ 5/12
+ * │ │ │ │ │ │ │ │ │ ┐ │
+ * │ ├───┼───┼───┼───┼───┼───┤ │ │ 1/6 │
+ * │ │ x │ │ │ │ │ │ │ ┘ ┘
+ * │ └───┴───┴───┴───┴───┴───┘ │
+ * │ │
+ * │ │
+ * │ │
+ * │ │
+ * │ │
+ * └───────────────────────────────────────────────┘
+ */
+g.test('minFilter,nearest').
+desc(
+ `
+ Test that for filterable formats, minFilter 'nearest' mode correctly modifies the sampling.
+ - format= {<filterable formats>}
+ - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+filter((t) => {
+ return (
+ kTextureFormatInfo[t.format].color.type === 'float' ||
+ kTextureFormatInfo[t.format].color.type === 'unfilterable-float');
+
+}).
+beginSubcases().
+combine('addressModeU', kAddressModes).
+combine('addressModeV', kAddressModes)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+}).
+fn((t) => {
+ const { format, addressModeU, addressModeV } = t.params;
+ const sampler = t.device.createSampler({
+ addressModeU,
+ addressModeV,
+ minFilter: 'nearest'
+ });
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+
+ struct VertexOut {
+ @builtin(position) pos: vec4f,
+ @location(0) uv: vec2f,
+ };
+
+ @vertex
+ fn vs_main(@builtin(vertex_index) vi : u32,
+ @builtin(instance_index) ii: u32) -> VertexOut {
+ const grid = vec2f(${kNearestRenderSize}, ${kNearestRenderSize});
+ const posBases = array(
+ vec2f(.5, .5), vec2f(.5, -.5), vec2f(-.5, -.5),
+ vec2f(.5, .5), vec2f(-.5, -.5), vec2f(-.5, .5),
+ );
+ // Choose UVs so that the quad ends up being the 6x6 texture.
+ const uvBases = array(
+ vec2f(2., -1.), vec2f(2., 2.), vec2f(-1., 2.),
+ vec2f(2., -1.), vec2f(-1., 2.), vec2f(-1., -1.),
+ );
+
+ let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y));
+
+ // Compute the offset of instance plane (pre-grid transformation).
+ const constantPlaneOffset = vec2f(5. / 12., 5. / 12.);
+ const perPixelOffset = vec2f(1. / 6., 1. / 6.);
+ let posBase = posBases[vi] + constantPlaneOffset - perPixelOffset * cell;
+
+ // Apply the grid transformation.
+ let cellOffset = cell / grid * 2;
+ let absPos = (posBase + 1) / grid - 1 + cellOffset;
+
+ return VertexOut(vec4f(absPos, 0.0, 1.0), uvBases[vi]);
+ }
+
+ @fragment
+ fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {
+ return textureSample(t, s, uv);
+ }
+ `
+ });
+ const vertexCount = 6;
+ const instanceCount = kNearestRenderDim.reduce((sink, current) => sink * current);
+ const render = t.runFilterRenderPipeline(
+ sampler,
+ module,
+ format,
+ kNearestRenderDim,
+ vertexCount,
+ instanceCount
+ );
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: render },
+ expectedColors(format, 'nearest', addressModeU, addressModeV),
+ kNearestRenderDim
+ );
+});
+
+/* The following diagram shows the sub-pixel quad and the relative distances between the sample
+ * points and the origin. The pixel is not shown in this diagram but is a 2x bounding box around the
+ * quad similar to the one in the diagram for minFilter,nearest above. The dark square is where the
+ * cell (0,0) is, and the offsets are all relative to that point.
+ *
+ * 11/32
+ * ┌─────────────┐
+ *
+ * 3/16 5/16 3/16
+ * ┌───────┬───────────┬───────┐
+ *
+ * ┌────┬────┬────┬────┬────┬────┬────┬────┐
+ * │ │ │ │ │ │ │ │ │
+ * │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤
+ * │ │□ │ □│ │ │□ │ □│ │ ┐
+ * │ │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤ │
+ * │ │ │ │ │ │ │ │ │ │ 1/4
+ * │ │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤ │
+ * │ │□ │ □│ │ │□ │ □│ │ ┤
+ * │ │ │ │ │ │ │ │ │ │
+ * ├────┼────┼────┼────x────┼────┼────┼────┤ │ 3/16 ┐
+ * │ │ │ │ │ │ │ │ │ │ │
+ * │ │□ │ □│ │ │□ │ □│ │ ┤ │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤ │ │
+ * │ │ │ │ │ │ │ │ │ │ │ 11/32
+ * │ │ │ │ │ │ │ │ │ │ 1/4 │
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤ │ │
+ * │ │ │ │ │ │ │ │ │ │ │
+ * │ │■ │ □│ │ │□ │ □│ │ ┘ ┘
+ * ├────┼────┼────┼────┼────┼────┼────┼────┤
+ * │ │ │ │ │ │ │ │ │
+ * │ │ │ │ │ │ │ │ │
+ * └────┴────┴────┴────┴────┴────┴────┴────┘
+ */
+g.test('minFilter,linear').
+desc(
+ `
+ Test that for filterable formats, minFilter 'linear' mode correctly modifies the sampling.
+ - format= {<filterable formats>}
+ - addressModeU= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ - addressModeV= {'clamp-to-edge', 'repeat', 'mirror-repeat'}
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+filter((t) => {
+ return (
+ kTextureFormatInfo[t.format].color.type === 'float' ||
+ kTextureFormatInfo[t.format].color.type === 'unfilterable-float');
+
+}).
+beginSubcases().
+combine('addressModeU', kAddressModes).
+combine('addressModeV', kAddressModes)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+}).
+fn((t) => {
+ const { format, addressModeU, addressModeV } = t.params;
+ const sampler = t.device.createSampler({
+ addressModeU,
+ addressModeV,
+ minFilter: 'linear'
+ });
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+
+ struct VertexOut {
+ @builtin(position) pos: vec4f,
+ @location(0) uv: vec2f,
+ };
+
+ @vertex
+ fn vs_main(@builtin(vertex_index) vi : u32,
+ @builtin(instance_index) ii: u32) -> VertexOut {
+ const grid = vec2f(${kLinearRenderSize}, ${kLinearRenderSize});
+ const posBases = array(
+ vec2f(.5, .5), vec2f(.5, -.5), vec2f(-.5, -.5),
+ vec2f(.5, .5), vec2f(-.5, -.5), vec2f(-.5, .5),
+ );
+ // Choose UVs so that the quad ends up being the 8x8 texture.
+ const uvBases = array(
+ vec2f(2.5, -1.5), vec2f(2.5, 2.5), vec2f(-1.5, 2.5),
+ vec2f(2.5, -1.5), vec2f(-1.5, 2.5), vec2f(-1.5, -1.5),
+ );
+
+ let cell = vec2f(f32(ii) % grid.x, floor(f32(ii) / grid.y));
+
+ // Compute the offset of instance plane (pre-grid transformation).
+ const constantPlaneOffset = vec2f(11. / 32., 11. / 32.);
+ const xOffsets = array(0., 3. / 16., 1. / 2., 11. / 16.);
+ const yOffsets = array(0., 1. / 4., 7. / 16., 11. / 16.);
+ let pixelOffset = vec2f(xOffsets[u32(cell.x)], yOffsets[u32(cell.y)]);
+ let posBase = posBases[vi] + constantPlaneOffset - pixelOffset;
+
+ // Compute the offset of instance plane.
+ let cellOffset = cell / grid * 2;
+ let absPos = (posBase + 1) / grid - 1 + cellOffset;
+
+ return VertexOut(vec4f(absPos, 0.0, 1.0), uvBases[vi]);
+ }
+
+ @fragment
+ fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {
+ return textureSample(t, s, uv);
+ }
+ `
+ });
+ const vertexCount = 6;
+ const instanceCount = kLinearRenderDim.reduce((sink, current) => sink * current);
+ const render = t.runFilterRenderPipeline(
+ sampler,
+ module,
+ format,
+ kLinearRenderDim,
+ vertexCount,
+ instanceCount
+ );
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: render },
+ expectedColors(format, 'linear', addressModeU, addressModeV),
+ kLinearRenderDim
+ );
+});
+
+g.test('mipmapFilter').
+desc(
+ `
+ Test that for filterable formats, mipmapFilter modes correctly modifies the sampling.
+ - format= {<filterable formats>}
+ - filterMode= {'nearest', 'linear'}
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+filter((t) => {
+ return (
+ kTextureFormatInfo[t.format].color.type === 'float' ||
+ kTextureFormatInfo[t.format].color.type === 'unfilterable-float');
+
+}).
+beginSubcases().
+combine('filterMode', kMipmapFilterModes)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ if (kTextureFormatInfo[t.params.format].color.type === 'unfilterable-float') {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+}).
+fn((t) => {
+ const { format, filterMode } = t.params;
+ // Takes a 8x8/4x4 mipmapped texture and renders it on multiple quads with different UVs such
+ // that each instanced quad from left to right emulates moving the quad further and further from
+ // the camera. Each quad is then rendered to a single pixel in a 1-dimensional texture. Since
+ // the 8x8 is fully black and the 4x4 is fully white, we should see the pixels increase in
+ // brightness from left to right when sampling linearly, and jump from black to white when
+ // sampling for the nearest mip level.
+ const kTextureSize = 8;
+ const kRenderSize = 8;
+
+ const sampler = t.device.createSampler({
+ mipmapFilter: filterMode
+ });
+ const sampleTexture = t.createTextureFromTexelViewsMultipleMipmaps(
+ [
+ TexelView.fromTexelsAsColors(format, () => {
+ return { R: 0.0, G: 0.0, B: 0.0, A: 1.0 };
+ }),
+ TexelView.fromTexelsAsColors(format, (_coords) => {
+ return { R: 1.0, G: 1.0, B: 1.0, A: 1.0 };
+ })],
+
+ {
+ size: [kTextureSize, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
+ }
+ );
+ const renderTexture = t.device.createTexture({
+ format,
+ size: [kRenderSize, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+
+ struct VertexOut {
+ @builtin(position) pos: vec4f,
+ @location(0) uv: vec2f,
+ };
+
+ @vertex
+ fn vs_main(@builtin(vertex_index) vi : u32,
+ @builtin(instance_index) ii: u32) -> VertexOut {
+ const grid = vec2f(${kRenderSize}., 1.);
+ const pos = array(
+ vec2f( 1.0, 1.0), vec2f( 1.0, -1.0), vec2f(-1.0, -1.0),
+ vec2f( 1.0, 1.0), vec2f(-1.0, -1.0), vec2f(-1.0, 1.0),
+ );
+ const uv = array(
+ vec2f(1., 0.), vec2f(1., 1.), vec2f(0., 1.),
+ vec2f(1., 0.), vec2f(0., 1.), vec2f(0., 0.),
+ );
+
+ // Compute the offset of the plane.
+ let cell = vec2f(f32(ii) % grid.x, 0.);
+ let cellOffset = cell / grid * 2;
+ let absPos = (pos[vi] + 1) / grid - 1 + cellOffset;
+ let uvFactor = (1. / 8.) * (1 + (f32(ii) / (grid.x - 1)));
+ return VertexOut(vec4f(absPos, 0.0, 1.0), uv[vi] * uvFactor);
+ }
+
+ @fragment
+ fn fs_main(@location(0) uv : vec2f) -> @location(0) vec4f {
+ return textureSample(t, s, uv);
+ }
+ `
+ });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs_main'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs_main',
+ targets: [{ format }]
+ }
+ });
+ const bindgroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: sampler },
+ { binding: 1, resource: sampleTexture.createView() }]
+
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTexture.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(0, bindgroup);
+ renderPass.draw(6, kRenderSize);
+ renderPass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ // Since mipmap filtering varies across different backends, we verify that the result exhibits
+ // filtered characteristics without strict value equalities via copies to a buffer.
+ const buffer = t.copyWholeTextureToNewBufferSimple(renderTexture, 0);
+ t.expectGPUBufferValuesPassCheck(
+ buffer,
+ (actual) => {
+ // Convert the buffer to texel view so we can do comparisons.
+ const layout = getTextureCopyLayout(format, '2d', [kRenderSize, 1, 1]);
+ const view = TexelView.fromTextureDataByReference(format, actual, {
+ bytesPerRow: layout.bytesPerRow,
+ rowsPerImage: layout.rowsPerImage,
+ subrectOrigin: [0, 0, 0],
+ subrectSize: [kRenderSize, 1, 1]
+ });
+
+ // We only check the R component for the conditions, since all components should be equal if
+ // specified in the format.
+ switch (filterMode) {
+ case 'linear':{
+ // For 'linear' mode, we check that the resulting 1d image is monotonically increasing.
+ for (let x = 1; x < kRenderSize; x++) {
+ const { R: Ri } = view.color({ x: x - 1, y: 0, z: 0 });
+ const { R: Rj } = view.color({ x, y: 0, z: 0 });
+ if (Ri >= Rj) {
+ return Error(
+ 'Linear filtering on mipmaps should be a monotonically increasing sequence:\n' +
+ view.toString(
+ { x: 0, y: 0, z: 0 },
+ { width: kRenderSize, height: 1, depthOrArrayLayers: 1 }
+ )
+ );
+ }
+ }
+ break;
+ }
+ case 'nearest':{
+ // For 'nearest' mode, we check that the resulting 1d image changes from 0.0 to 1.0
+ // exactly once.
+ let changes = 0;
+ for (let x = 1; x < kRenderSize; x++) {
+ const { R: Ri } = view.color({ x: x - 1, y: 0, z: 0 });
+ const { R: Rj } = view.color({ x, y: 0, z: 0 });
+ if (Ri !== Rj) {
+ changes++;
+ }
+ }
+ if (changes !== 1) {
+ return Error(
+ `Nearest filtering on mipmaps should change exacly once but found (${changes}):\n` +
+ view.toString(
+ { x: 0, y: 0, z: 0 },
+ { width: kRenderSize, height: 1, depthOrArrayLayers: 1 }
+ )
+ );
+ }
+ break;
+ }
+ }
+ return undefined;
+ },
+ { srcByteOffset: 0, type: Uint8Array, typedLength: buffer.size }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/lod_clamp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/lod_clamp.spec.js
new file mode 100644
index 0000000000..07c36e85aa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/sampling/lod_clamp.spec.js
@@ -0,0 +1,12 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests the behavior of LOD clamping (lodMinClamp, lodMaxClamp).
+
+TODO:
+- Write a test that can test the exact clamping behavior
+- Test a bunch of values, including very large/small ones.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/shader_module/compilation_info.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/shader_module/compilation_info.spec.js
new file mode 100644
index 0000000000..9f9a1fcba0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/shader_module/compilation_info.spec.js
@@ -0,0 +1,264 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+ShaderModule CompilationInfo tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { assert } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kValidShaderSources = [
+{
+ valid: true,
+ name: 'ascii',
+ _code: `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+},
+{
+ valid: true,
+ name: 'unicode',
+ _code: `
+ // 頂点シェーダー 👩‍💻
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+}];
+
+
+const kInvalidShaderSources = [
+{
+ valid: false,
+ name: 'ascii',
+ _errorLine: 4,
+ _code: `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ // Expected Error: unknown function 'unknown'
+ return unknown(0.0, 0.0, 0.0, 1.0);
+ }`
+},
+{
+ valid: false,
+ name: 'unicode',
+ _errorLine: 5,
+ _code: `
+ // 頂点シェーダー 👩‍💻
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ // Expected Error: unknown function 'unknown'
+ return unknown(0.0, 0.0, 0.0, 1.0);
+ }`
+},
+{
+ valid: false,
+ name: 'carriage-return',
+ _errorLine: 5,
+ _code:
+ `
+ @vertex fn main() -> @builtin(position) vec4<f32> {` +
+ '\r\n' +
+ `
+ // Expected Error: unknown function 'unknown'
+ return unknown(0.0, 0.0, 0.0, 1.0);
+ }`
+}];
+
+
+const kAllShaderSources = [...kValidShaderSources, ...kInvalidShaderSources];
+
+// This is the source the sourcemap refers to.
+const kOriginalSource = new Array(20).
+fill(0).
+map((_, i) => `original line ${i}`).
+join('\n');
+
+const kSourceMaps = {
+ none: undefined,
+ empty: {},
+ // A valid source map. It maps `unknown` on lines 4 and line 5 to
+ // `wasUnknown` from lines 20, 21 respectively
+ valid: {
+ version: 3,
+ sources: ['myCode'],
+ sourcesContent: [kOriginalSource],
+ names: ['myMain', 'wasUnknown'],
+ mappings: ';kBAYkCA,OACd;SAElB;gBAKOC;gBACAA'
+ },
+ // not a valid sourcemap
+ invalid: {
+ version: -123,
+ notAnything: {}
+ },
+ // The correct format but this data is for lines 11,12 even
+ // though the source only has 5 or 6 lines
+ nonMatching: {
+ version: 3,
+ sources: ['myCode'],
+ sourcesContent: [kOriginalSource],
+ names: ['myMain'],
+ mappings: ';;;;;;;;;;kBAYkCA,OACd;SAElB'
+ }
+};
+const kSourceMapsKeys = keysOf(kSourceMaps);
+
+g.test('getCompilationInfo_returns').
+desc(
+ `
+ Test that getCompilationInfo() can be called on any ShaderModule.
+
+ Note: sourcemaps are not used in the WebGPU API. We are only testing that
+ browser that happen to use them don't fail or crash if the sourcemap is
+ bad or invalid.
+
+ - Test for both valid and invalid shader modules.
+ - Test for shader modules containing only ASCII and those containing unicode characters.
+ - Test that the compilation info for valid shader modules contains no errors.
+ - Test that the compilation info for invalid shader modules contains at least one error.`
+).
+params((u) =>
+u.combineWithParams(kAllShaderSources).beginSubcases().combine('sourceMapName', kSourceMapsKeys)
+).
+fn(async (t) => {
+ const { _code, valid, sourceMapName } = t.params;
+
+ const shaderModule = t.expectGPUError(
+ 'validation',
+ () => {
+ const sourceMap = kSourceMaps[sourceMapName];
+ return t.device.createShaderModule({ code: _code, ...(sourceMap && { sourceMap }) });
+ },
+ !valid
+ );
+
+ const info = await shaderModule.getCompilationInfo();
+
+ t.expect(
+ info instanceof GPUCompilationInfo,
+ 'Expected a GPUCompilationInfo object to be returned'
+ );
+
+ // Expect that we get zero error messages from a valid shader.
+ // Message types other than errors are OK.
+ let errorCount = 0;
+ for (const message of info.messages) {
+ if (message.type === 'error') {
+ errorCount++;
+ }
+ }
+ if (valid) {
+ t.expect(errorCount === 0, "Expected zero GPUCompilationMessages of type 'error'");
+ } else {
+ t.expect(errorCount > 0, "Expected at least one GPUCompilationMessages of type 'error'");
+ }
+});
+
+g.test('line_number_and_position').
+desc(
+ `
+ Test that line numbers reported by compilationInfo either point at an appropriate line and
+ position or at 0:0, indicating an unknown position.
+
+ Note: sourcemaps are not used in the WebGPU API. We are only testing that
+ browser that happen to use them don't fail or crash if the sourcemap is
+ bad or invalid.
+
+ - Test for invalid shader modules containing containing at least one error.
+ - Test for shader modules containing only ASCII and those containing unicode characters.`
+).
+params((u) =>
+u.
+combineWithParams(kInvalidShaderSources).
+beginSubcases().
+combine('sourceMapName', kSourceMapsKeys)
+).
+fn(async (t) => {
+ const { _code, _errorLine, sourceMapName } = t.params;
+
+ const shaderModule = t.expectGPUError('validation', () => {
+ const sourceMap = kSourceMaps[sourceMapName];
+ return t.device.createShaderModule({ code: _code, ...(sourceMap && { sourceMap }) });
+ });
+
+ const info = await shaderModule.getCompilationInfo();
+
+ let foundAppropriateError = false;
+ for (const message of info.messages) {
+ if (message.type === 'error') {
+ // Some backends may not be able to indicate a precise location for the error. In those
+ // cases a line and position of 0 should be reported.
+ // 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."
+ );
+
+ if (message.lineNum === 0 || message.lineNum === _errorLine) {
+ foundAppropriateError = true;
+
+ // Various backends may choose to report the error at different positions within the line,
+ // so it's difficult to meaningfully validate them.
+ break;
+ }
+ }
+ }
+ t.expect(
+ foundAppropriateError,
+ 'Expected to find an error which corresponded with the erroneous line'
+ );
+});
+
+g.test('offset_and_length').
+desc(
+ `Test that message offsets and lengths are valid and align with any reported lineNum and linePos.
+
+ Note: sourcemaps are not used in the WebGPU API. We are only testing that
+ browser that happen to use them don't fail or crash if the sourcemap is
+ bad or invalid.
+
+ - Test for valid and invalid shader modules.
+ - Test for shader modules containing only ASCII and those containing unicode characters.`
+).
+params((u) =>
+u.combineWithParams(kAllShaderSources).beginSubcases().combine('sourceMapName', kSourceMapsKeys)
+).
+fn(async (t) => {
+ const { _code, valid, sourceMapName } = t.params;
+
+ const shaderModule = t.expectGPUError(
+ 'validation',
+ () => {
+ const sourceMap = kSourceMaps[sourceMapName];
+ return t.device.createShaderModule({ code: _code, ...(sourceMap && { sourceMap }) });
+ },
+ !valid
+ );
+
+ const info = await shaderModule.getCompilationInfo();
+
+ 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'
+ );
+
+ // If a valid line number and position are given, the offset should point the the same
+ // location in the shader source.
+ if (message.lineNum !== 0 && message.linePos !== 0) {
+ let lineOffset = 0;
+ for (let i = 0; i < message.lineNum - 1; ++i) {
+ lineOffset = _code.indexOf('\n', lineOffset);
+ assert(lineOffset !== -1);
+ lineOffset += 1;
+ }
+
+ t.expect(
+ message.offset === lineOffset + message.linePos - 1,
+ 'lineNum and linePos should point to the same location as offset'
+ );
+ }
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/format_reinterpretation.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/format_reinterpretation.spec.js
new file mode 100644
index 0000000000..efe3cf3265
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/format_reinterpretation.spec.js
@@ -0,0 +1,358 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test texture views can reinterpret the format of the original texture.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ kRenderableColorTextureFormats,
+ kRegularTextureFormats,
+ viewCompatible } from
+
+'../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+const kColors = [
+{ 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 }];
+
+
+const kTextureSize = 16;
+
+function makeInputTexelView(format) {
+ return TexelView.fromTexelsAsColors(
+ format,
+ (coords) => {
+ const pixelPos = coords.y * kTextureSize + coords.x;
+ return kColors[pixelPos % kColors.length];
+ },
+ { clampToFormatRange: true }
+ );
+}
+
+function makeBlitPipeline(
+device,
+format,
+multisample)
+{
+ return device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: device.createShaderModule({
+ code: `
+ @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ 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));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module:
+ multisample.sample > 1 ?
+ device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var src: texture_multisampled_2d<f32>;
+ @fragment fn main(@builtin(position) coord: vec4<f32>) -> @location(0) vec4<f32> {
+ var result : vec4<f32>;
+ for (var i = 0; i < ${multisample.sample}; i = i + 1) {
+ result = result + textureLoad(src, vec2<i32>(coord.xy), i);
+ }
+ return result * ${1 / multisample.sample};
+ }`
+ }) :
+ device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var src: texture_2d<f32>;
+ @fragment fn main(@builtin(position) coord: vec4<f32>) -> @location(0) vec4<f32> {
+ return textureLoad(src, vec2<i32>(coord.xy), 0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ multisample: {
+ count: multisample.render
+ }
+ });
+}
+
+g.test('texture_binding').
+desc(`Test that a regular texture allocated as 'format' is correctly sampled as 'viewFormat'.`).
+params((u) =>
+u //
+.combine('format', kRegularTextureFormats).
+combine('viewFormat', kRegularTextureFormats).
+filter(
+ ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+)
+).
+beforeAllSubcases((t) => {
+ const { format, viewFormat } = t.params;
+ t.skipIfTextureFormatNotSupported(format, viewFormat);
+}).
+fn((t) => {
+ const { format, viewFormat } = t.params;
+
+ // Make an input texel view.
+ const inputTexelView = makeInputTexelView(format);
+
+ // Create the initial texture with the contents if the input texel view.
+ const texture = t.createTextureFromTexelView(inputTexelView, {
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ viewFormats: [viewFormat]
+ });
+
+ // Reinterpret the texture as the view format.
+ // Make a texel view of the format that also reinterprets the data.
+ const reinterpretedView = texture.createView({ format: viewFormat });
+ const reinterpretedTexelView = TexelView.fromTexelsAsBytes(viewFormat, inputTexelView.bytes);
+
+ // Create a pipeline to write data out to rgba8unorm.
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var src: texture_2d<f32>;
+ @group(0) @binding(1) var dst: texture_storage_2d<rgba8unorm, write>;
+ @compute @workgroup_size(1, 1) fn main(
+ @builtin(global_invocation_id) global_id: vec3<u32>,
+ ) {
+ var coord = vec2<i32>(global_id.xy);
+ textureStore(dst, coord, textureLoad(src, coord, 0));
+ }`
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Create an rgba8unorm output texture.
+ const outputTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC
+ })
+ );
+
+ // Execute a compute pass to load data from the reinterpreted view and
+ // write out to the rgba8unorm texture.
+ 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: reinterpretedView
+ },
+ {
+ binding: 1,
+ resource: outputTexture.createView()
+ }]
+
+ })
+ );
+ pass.dispatchWorkgroups(kTextureSize, kTextureSize);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: outputTexture },
+ TexelView.fromTexelsAsColors('rgba8unorm', reinterpretedTexelView.color, {
+ clampToFormatRange: true
+ }),
+ [kTextureSize, kTextureSize]
+ );
+});
+
+g.test('render_and_resolve_attachment').
+desc(
+ `Test that a color render attachment allocated as 'format' is correctly rendered to as 'viewFormat',
+and resolved to an attachment allocated as 'format' viewed as 'viewFormat'.
+
+Other combinations aren't possible because the render and resolve targets must both match
+in view format and match in base format.`
+).
+params((u) =>
+u //
+.combine('format', kRenderableColorTextureFormats).
+combine('viewFormat', kRenderableColorTextureFormats).
+filter(
+ ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+).
+combine('sampleCount', [1, 4])
+).
+beforeAllSubcases((t) => {
+ const { format, viewFormat } = t.params;
+ t.skipIfTextureFormatNotSupported(format, viewFormat);
+}).
+fn((t) => {
+ const { format, viewFormat, sampleCount } = t.params;
+
+ // Make an input texel view.
+ const inputTexelView = makeInputTexelView(format);
+
+ // Create the renderTexture as |format|.
+ const renderTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT | (
+ sampleCount > 1 ? GPUTextureUsage.TEXTURE_BINDING : GPUTextureUsage.COPY_SRC),
+ viewFormats: [viewFormat],
+ sampleCount
+ })
+ );
+
+ const resolveTexture =
+ sampleCount === 1 ?
+ undefined :
+ t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ viewFormats: [viewFormat]
+ })
+ );
+
+ // Create the sample source with the contents of the input texel view.
+ // We will sample this texture into |renderTexture|. It uses the same format to keep the same
+ // number of bits of precision.
+ const sampleSource = t.createTextureFromTexelView(inputTexelView, {
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ // Reinterpret the renderTexture as |viewFormat|.
+ const reinterpretedRenderView = renderTexture.createView({ format: viewFormat });
+ const reinterpretedResolveView =
+ resolveTexture && resolveTexture.createView({ format: viewFormat });
+
+ // Create a pipeline to blit a src texture to the render attachment.
+ const pipeline = makeBlitPipeline(t.device, viewFormat, {
+ sample: 1,
+ render: sampleCount
+ });
+
+ // Execute a render pass to sample |sampleSource| into |texture| viewed as |viewFormat|.
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: reinterpretedRenderView,
+ resolveTarget: reinterpretedResolveView,
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: sampleSource.createView()
+ }]
+
+ })
+ );
+ pass.draw(6);
+ pass.end();
+
+ // If the render target is multisampled, we'll manually resolve it to check
+ // the contents.
+ const singleSampleRenderTexture = resolveTexture ?
+ t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ ) :
+ renderTexture;
+
+ if (resolveTexture) {
+ // Create a pipeline to blit the multisampled render texture to a non-multisample texture.
+ // We are basically performing a manual resolve step to the same format as the original
+ // render texture to check its contents.
+ const pipeline = makeBlitPipeline(t.device, format, { sample: sampleCount, render: 1 });
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: singleSampleRenderTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: renderTexture.createView()
+ }]
+
+ })
+ );
+ pass.draw(6);
+ pass.end();
+ }
+
+ // Submit the commands.
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ // Check the rendered contents.
+ const renderViewTexels = TexelView.fromTexelsAsColors(viewFormat, inputTexelView.color, {
+ clampToFormatRange: true
+ });
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: singleSampleRenderTexture },
+ renderViewTexels,
+ [kTextureSize, kTextureSize],
+ { maxDiffULPsForNormFormat: 2 }
+ );
+
+ // Check the resolved contents.
+ if (resolveTexture) {
+ const resolveView = TexelView.fromTexelsAsColors(viewFormat, renderViewTexels.color, {
+ clampToFormatRange: true
+ });
+ t.expectTexelViewComparisonIsOkInTexture(
+ { texture: resolveTexture },
+ resolveView,
+ [kTextureSize, kTextureSize],
+ { maxDiffULPsForNormFormat: 2 }
+ );
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/read.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/read.spec.js
new file mode 100644
index 0000000000..193c3074e1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/read.spec.js
@@ -0,0 +1,56 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test the result of reading textures through texture views with various options.
+
+All x= every possible view read method: {
+ - {unfiltered, filtered (if valid), comparison (if valid)} sampling
+ - storage read {vertex, fragment, compute}
+ - no-op render pass that loads and then stores
+ - depth comparison
+ - stencil comparison
+}
+
+Format reinterpretation is not tested here. It is in format_reinterpretation.spec.ts.
+
+TODO: Write helper for this if not already available (see resource_init, buffer_sync_test for related code).
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('format').
+desc(
+ `Views of every allowed format.
+
+- x= every texture format
+- x= sampleCount {1, 4} if valid
+- x= every possible view read method (see above)
+`
+).
+unimplemented();
+
+g.test('dimension').
+desc(
+ `Views of every allowed dimension.
+
+- x= a representative subset of formats
+- x= {every texture dimension} x {every valid view dimension}
+ (per gpuweb#79 no dimension-count reinterpretations, like 2d-array <-> 3d, are possible)
+- x= sampleCount {1, 4} if valid
+- x= every possible view read method (see above)
+`
+).
+unimplemented();
+
+g.test('aspect').
+desc(
+ `Views of every allowed aspect of depth/stencil textures.
+
+- x= every depth/stencil format
+- x= {"all", "stencil-only", "depth-only"} where valid for the format
+- x= sampleCount {1, 4} if valid
+- x= every possible view read method (see above)
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/write.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/write.spec.js
new file mode 100644
index 0000000000..5b52d9d0b2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/texture_view/write.spec.js
@@ -0,0 +1,54 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test the result of writing textures through texture views with various options.
+
+All x= every possible view write method: {
+ - storage write {fragment, compute}
+ - render pass store
+ - render pass resolve
+}
+
+Format reinterpretation is not tested here. It is in format_reinterpretation.spec.ts.
+
+TODO: Write helper for this if not already available (see resource_init, buffer_sync_test for related code).
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('format').
+desc(
+ `Views of every allowed format.
+
+- x= every texture format
+- x= sampleCount {1, 4} if valid
+- x= every possible view write method (see above)
+`
+).
+unimplemented();
+
+g.test('dimension').
+desc(
+ `Views of every allowed dimension.
+
+- x= a representative subset of formats
+- x= {every texture dimension} x {every valid view dimension}
+ (per gpuweb#79 no dimension-count reinterpretations, like 2d-array <-> 3d, are possible)
+- x= sampleCount {1, 4} if valid
+- x= every possible view write method (see above)
+`
+).
+unimplemented();
+
+g.test('aspect').
+desc(
+ `Views of every allowed aspect of depth/stencil textures.
+
+- x= every depth/stencil format
+- x= {"all", "stencil-only", "depth-only"} where valid for the format
+- x= sampleCount {1, 4} if valid
+- x= every possible view write method (see above)
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/uncapturederror.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/uncapturederror.spec.js
new file mode 100644
index 0000000000..3c7615f961
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/uncapturederror.spec.js
@@ -0,0 +1,34 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for GPUDevice.onuncapturederror.
+`;import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+
+export const g = makeTestGroup(Fixture);
+
+g.test('constructor').
+desc(
+ `GPUUncapturedErrorEvent constructor options (also tests constructing GPUOutOfMemoryError/GPUValidationError)`
+).
+unimplemented();
+
+g.test('iff_uncaptured').
+desc(
+ `{validation, out-of-memory} error should fire uncapturederror iff not captured by a scope.`
+).
+unimplemented();
+
+g.test('only_original_device_is_event_target').
+desc(
+ `Original GPUDevice objects are EventTargets and have onuncapturederror, but
+deserialized GPUDevices do not.`
+).
+unimplemented();
+
+g.test('uncapturederror_from_non_originating_thread').
+desc(
+ `Uncaptured errors on any thread should always propagate to the original GPUDevice object
+(since deserialized ones don't have EventTarget/onuncapturederror).`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/correctness.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/correctness.spec.js
new file mode 100644
index 0000000000..0973299a73
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/correctness.spec.js
@@ -0,0 +1,1180 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+TODO: Test more corner case values for Float16 / Float32 (INF, NaN, ...) and reduce the
+float tolerance.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ assert,
+ filterUniqueValueTestVariants,
+ makeValueTestVariant,
+ memcpy,
+ unreachable } from
+'../../../../common/util/util.js';
+import {
+ kPerStageBindingLimits,
+ kVertexFormatInfo,
+ kVertexFormats } from
+'../../../capability_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { float32ToFloat16Bits, normalizedIntegerAsFloat } from '../../../util/conversion.js';
+import { align, clamp } from '../../../util/math.js';
+
+// These types mirror the structure of GPUVertexBufferLayout but allow defining the extra
+// dictionary members at the GPUVertexBufferLayout and GPUVertexAttribute level. The are used
+// like so:
+//
+// VertexState<{arrayStride: number}, {format: VertexFormat}>
+// VertexBuffer<{arrayStride: number}, {format: VertexFormat}>
+// VertexAttrib<{format: VertexFormat}>
+
+
+
+
+
+
+
+
+
+
+
+
+function mapBufferAttribs(
+buffer,
+f)
+{
+ const newAttributes = [];
+ for (const a of buffer.attributes) {
+ newAttributes.push({
+ shaderLocation: a.shaderLocation,
+ ...f(buffer, a)
+ });
+ }
+
+ return { ...buffer, attributes: newAttributes };
+}
+
+function mapStateAttribs(
+buffers,
+f)
+{
+ return buffers.map((b) => mapBufferAttribs(b, f));
+}
+
+function makeRgb10a2(rgba) {
+ const [r, g, b, a] = rgba;
+ assert((r & 0x3ff) === r);
+ assert((g & 0x3ff) === g);
+ assert((b & 0x3ff) === b);
+ assert((a & 0x3) === a);
+ return r | g << 10 | b << 20 | a << 30;
+}
+
+function normalizeRgb10a2(rgba, index) {
+ const normalizationFactor = index % 4 === 3 ? 3 : 1023;
+ return rgba / normalizationFactor;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+class VertexStateTest extends GPUTest {
+ // Generate for VS + FS (entrypoints vsMain / fsMain) that for each attribute will check that its
+ // value corresponds to what's expected (as provided by a uniform buffer per attribute) and then
+ // renders each vertex at position (vertexIndex, instanceindex) with either 1 (success) or
+ // a negative number corresponding to the check number (in case you need to debug a failure).
+ makeTestWGSL(
+ buffers,
+
+
+
+
+
+
+
+
+ vertexCount,
+ instanceCount)
+ {
+ // In the base WebGPU spec maxVertexAttributes is larger than maxUniformBufferPerStage. We'll
+ // use a combination of uniform and storage buffers to cover all possible attributes. This
+ // happens to work because maxUniformBuffer + maxStorageBuffer = 12 + 8 = 20 which is larger
+ // than maxVertexAttributes = 16.
+ // However this might not work in the future for implementations that allow even more vertex
+ // attributes so there will need to be larger changes when that happens.
+ const maxUniformBuffers = this.getDefaultLimit(kPerStageBindingLimits['uniformBuf'].maxLimit);
+ assert(
+ maxUniformBuffers + this.getDefaultLimit(kPerStageBindingLimits['storageBuf'].maxLimit) >=
+ this.device.limits.maxVertexAttributes
+ );
+
+ let vsInputs = '';
+ let vsChecks = '';
+ let vsBindings = '';
+
+ for (const b of buffers) {
+ for (const a of b.attributes) {
+ const format = kVertexFormatInfo[a.format];
+ const shaderComponentCount = a.shaderComponentCount ?? format.componentCount;
+ const i = a.shaderLocation;
+
+ // shaderType is either a scalar type like f32 or a vecN<scalarType>
+ let shaderType = a.shaderBaseType;
+ if (shaderComponentCount !== 1) {
+ shaderType = `vec${shaderComponentCount}<${shaderType}>`;
+ }
+
+ let maxCount = `${vertexCount}`;
+ let indexBuiltin = `input.vertexIndex`;
+ if (b.stepMode === 'instance') {
+ maxCount = `${instanceCount}`;
+ indexBuiltin = `input.instanceIndex`;
+ }
+
+ // Start using storage buffers when we run out of uniform buffers.
+ let storageType = 'uniform';
+ if (i >= maxUniformBuffers) {
+ storageType = 'storage, read';
+ }
+
+ vsInputs += ` @location(${i}) attrib${i} : ${shaderType},\n`;
+ vsBindings += `struct S${i} { data : array<vec4<${a.shaderBaseType}>, ${maxCount}> };\n`;
+ vsBindings += `@group(0) @binding(${i}) var<${storageType}> providedData${i} : S${i};\n`;
+
+ // Generate the all the checks for the attributes.
+ for (let component = 0; component < shaderComponentCount; component++) {
+ // Components are filled with (0, 0, 0, 1) if they aren't provided data from the pipeline.
+ if (component >= format.componentCount) {
+ const expected = component === 3 ? '1' : '0';
+ vsChecks += ` check(input.attrib${i}[${component}] == ${a.shaderBaseType}(${expected}));\n`;
+ continue;
+ }
+
+ // Check each component individually, with special handling of tolerance for floats.
+ const attribComponent =
+ shaderComponentCount === 1 ? `input.attrib${i}` : `input.attrib${i}[${component}]`;
+ const providedData = `providedData${i}.data[${indexBuiltin}][${component}]`;
+ if (format.type === 'uint' || format.type === 'sint') {
+ vsChecks += ` check(${attribComponent} == ${providedData});\n`;
+ } else {
+ vsChecks += ` check(floatsSimilar(${attribComponent}, ${providedData}, f32(${
+ a.floatTolerance ?? 0
+ })));\n`;
+ }
+ }
+ }
+ }
+
+ return `
+struct Inputs {
+${vsInputs}
+ @builtin(vertex_index) vertexIndex: u32,
+ @builtin(instance_index) instanceIndex: u32,
+};
+
+${vsBindings}
+
+var<private> vsResult : i32 = 1;
+var<private> checkIndex : i32 = 0;
+fn check(success : bool) {
+ if (!success) {
+ vsResult = -checkIndex;
+ }
+ checkIndex = checkIndex + 1;
+}
+
+fn floatsSimilar(a : f32, b : f32, tolerance : f32) -> bool {
+ // Note: -0.0 and 0.0 have different bit patterns, but compare as equal.
+ return abs(a - b) < tolerance;
+}
+
+fn doTest(input : Inputs) {
+${vsChecks}
+}
+
+struct VSOutputs {
+ @location(0) @interpolate(flat) result : i32,
+ @builtin(position) position : vec4<f32>,
+};
+
+@vertex fn vsMain(input : Inputs) -> VSOutputs {
+ doTest(input);
+
+ // Place that point at pixel (vertexIndex, instanceIndex) in a framebuffer of size
+ // (vertexCount , instanceCount).
+ var output : VSOutputs;
+ output.position = vec4<f32>(
+ ((f32(input.vertexIndex) + 0.5) / ${vertexCount}.0 * 2.0) - 1.0,
+ ((f32(input.instanceIndex) + 0.5) / ${instanceCount}.0 * 2.0) - 1.0,
+ 0.0, 1.0
+ );
+ output.result = vsResult;
+ return output;
+}
+
+@fragment fn fsMain(@location(0) @interpolate(flat) result : i32)
+ -> @location(0) i32 {
+ return result;
+}
+ `;
+ }
+
+ makeTestPipeline(
+ buffers,
+
+
+
+
+
+
+
+
+
+ vertexCount,
+ instanceCount)
+ {
+ const module = this.device.createShaderModule({
+ code: this.makeTestWGSL(buffers, vertexCount, instanceCount)
+ });
+
+ const bufferLayouts = [];
+ for (const b of buffers) {
+ bufferLayouts[b.slot] = b;
+ }
+
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vsMain',
+ buffers: bufferLayouts
+ },
+ primitive: {
+ topology: 'point-list'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fsMain',
+ targets: [
+ {
+ format: 'r32sint'
+ }]
+
+ }
+ });
+ }
+
+ // Runs the render pass drawing points in a vertexCount*instanceCount rectangle, then check each
+ // of produced a value of 1 which means that the tests in the shader passed.
+ submitRenderPass(
+ pipeline,
+ buffers,
+ expectedData,
+ vertexCount,
+ instanceCount)
+ {
+ const testTexture = this.device.createTexture({
+ format: 'r32sint',
+ size: [vertexCount, instanceCount],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: testTexture.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, expectedData);
+ for (const buffer of buffers) {
+ pass.setVertexBuffer(buffer.slot, buffer.buffer, buffer.vbOffset ?? 0);
+ }
+ pass.draw(vertexCount, instanceCount);
+ pass.end();
+
+ this.device.queue.submit([encoder.finish()]);
+
+ this.expectSingleColor(testTexture, 'r32sint', {
+ size: [vertexCount, instanceCount, 1],
+ exp: { R: 1 }
+ });
+ }
+
+ // Generate TestData for the format with interesting test values.
+ // MAINTENANCE_TODO cache the result on the fixture?
+ // Note that the test data always starts with an interesting value, so that using the first
+ // test value in a test is still meaningful.
+ generateTestData(format) {
+ const formatInfo = kVertexFormatInfo[format];
+ const bitSize =
+ formatInfo.bytesPerComponent === 'packed' ? 0 : formatInfo.bytesPerComponent * 8;
+
+ switch (formatInfo.type) {
+ case 'float':{
+ // -0.0 and +0.0 have different bit patterns, but compare as equal.
+ const data = [42.42, 0.0, -0.0, 1.0, -1.0, 1000, -18.7, 25.17];
+ const expectedData = new Float32Array(data).buffer;
+ const vertexData =
+ bitSize === 32 ?
+ expectedData :
+ bitSize === 16 ?
+ new Uint16Array(data.map(float32ToFloat16Bits)).buffer :
+ unreachable();
+
+ return {
+ shaderBaseType: 'f32',
+ testComponentCount: data.length,
+ expectedData,
+ vertexData,
+ floatTolerance: 0.05
+ };
+ }
+
+ case 'sint':{
+
+ const data = [
+ 42,
+ 0, 1, 2, 3, 4, 5,
+ -1, -2, -3, -4, -5,
+ Math.pow(2, bitSize - 2),
+ Math.pow(2, bitSize - 1) - 1, // max value
+ -Math.pow(2, bitSize - 2),
+ -Math.pow(2, bitSize - 1) // min value
+ ];
+ const expectedData = new Int32Array(data).buffer;
+ const vertexData =
+ bitSize === 32 ?
+ expectedData :
+ bitSize === 16 ?
+ new Int16Array(data).buffer :
+ new Int8Array(data).buffer;
+
+ return {
+ shaderBaseType: 'i32',
+ testComponentCount: data.length,
+ expectedData,
+ vertexData
+ };
+ }
+
+ case 'uint':{
+
+ const data = [
+ 42,
+ 0, 1, 2, 3, 4, 5,
+ Math.pow(2, bitSize - 1),
+ Math.pow(2, bitSize) - 1 // max value
+ ];
+ const expectedData = new Uint32Array(data).buffer;
+ const vertexData =
+ bitSize === 32 ?
+ expectedData :
+ bitSize === 16 ?
+ new Uint16Array(data).buffer :
+ new Uint8Array(data).buffer;
+
+ return {
+ shaderBaseType: 'u32',
+ testComponentCount: data.length,
+ expectedData,
+ vertexData
+ };
+ }
+
+ case 'snorm':{
+
+ const data = [
+ 42,
+ 0, 1, 2, 3, 4, 5,
+ -1, -2, -3, -4, -5,
+ Math.pow(2, bitSize - 2),
+ Math.pow(2, bitSize - 1) - 1, // max value
+ -Math.pow(2, bitSize - 2),
+ -Math.pow(2, bitSize - 1) // min value
+ ];
+ const vertexData =
+ bitSize === 16 ?
+ new Int16Array(data).buffer :
+ bitSize === 8 ?
+ new Int8Array(data).buffer :
+ unreachable();
+
+ return {
+ shaderBaseType: 'f32',
+ testComponentCount: data.length,
+ expectedData: new Float32Array(data.map((v) => normalizedIntegerAsFloat(v, bitSize, true))).
+ buffer,
+ vertexData,
+ floatTolerance: 0.1 * normalizedIntegerAsFloat(1, bitSize, true)
+ };
+ }
+
+ case 'unorm':{
+ if (formatInfo.bytesPerComponent === 'packed') {
+ assert(format === 'unorm10-10-10-2'); // This is the only packed format for now.
+ assert(bitSize === 0);
+
+
+ const data = [
+ [0, 0, 0, 0],
+ [1023, 1023, 1023, 3],
+ [243, 567, 765, 2]];
+
+ const vertexData = new Uint32Array(data.map(makeRgb10a2)).buffer;
+ const expectedData = new Float32Array(data.flat().map(normalizeRgb10a2)).buffer;
+
+ return {
+ shaderBaseType: 'f32',
+ testComponentCount: data.flat().length,
+ expectedData,
+ vertexData,
+ floatTolerance: 0.1 / 1023
+ };
+ }
+
+
+ const data = [
+ 42,
+ 0, 1, 2, 3, 4, 5,
+ Math.pow(2, bitSize - 1),
+ Math.pow(2, bitSize) - 1 // max value
+ ];
+ const vertexData =
+ bitSize === 16 ?
+ new Uint16Array(data).buffer :
+ bitSize === 8 ?
+ new Uint8Array(data).buffer :
+ unreachable();
+
+ return {
+ shaderBaseType: 'f32',
+ testComponentCount: data.length,
+ expectedData: new Float32Array(data.map((v) => normalizedIntegerAsFloat(v, bitSize, false))).
+ buffer,
+ vertexData,
+ floatTolerance: 0.1 * normalizedIntegerAsFloat(1, bitSize, false)
+ };
+ }
+ }
+ }
+
+ // The TestData generated for a format might not contain enough data for all the vertices we are
+ // going to draw, so we expand them by adding additional copies of the vertexData as needed.
+ // expectedData is a bit different because it also needs to be unpacked to have `componentCount`
+ // components every 4 components (because the shader uses vec4 for the expected data).
+ expandTestData(data, maxCount, componentCount) {
+ const vertexComponentSize = data.vertexData.byteLength / data.testComponentCount;
+ const expectedComponentSize = data.expectedData.byteLength / data.testComponentCount;
+
+ const expandedVertexData = new Uint8Array(maxCount * componentCount * vertexComponentSize);
+ const expandedExpectedData = new Uint8Array(4 * maxCount * expectedComponentSize);
+
+ for (let index = 0; index < maxCount; index++) {
+ for (let component = 0; component < componentCount; component++) {
+ // If only we had some builtin JS memcpy function between ArrayBuffers...
+ const targetVertexOffset = (index * componentCount + component) * vertexComponentSize;
+ const sourceVertexOffset = targetVertexOffset % data.vertexData.byteLength;
+ memcpy(
+ { src: data.vertexData, start: sourceVertexOffset, length: vertexComponentSize },
+ { dst: expandedVertexData, start: targetVertexOffset }
+ );
+
+ const targetExpectedOffset = (index * 4 + component) * expectedComponentSize;
+ const sourceExpectedOffset =
+ (index * componentCount + component) * expectedComponentSize %
+ data.expectedData.byteLength;
+ memcpy(
+ { src: data.expectedData, start: sourceExpectedOffset, length: expectedComponentSize },
+ { dst: expandedExpectedData, start: targetExpectedOffset }
+ );
+ }
+ }
+
+ return {
+ shaderBaseType: data.shaderBaseType,
+ testComponentCount: maxCount * componentCount,
+ floatTolerance: data.floatTolerance,
+ expectedData: expandedExpectedData.buffer,
+ vertexData: expandedVertexData.buffer
+ };
+ }
+
+ // Copies `size` bytes from `source` to `target` starting at `offset` each `targetStride`.
+ // (the data in `source` is assumed packed)
+ interleaveVertexDataInto(
+ target,
+ src,
+ { targetStride, offset, size })
+ {
+ const dst = new Uint8Array(target);
+ for (
+ let srcStart = 0, dstStart = offset;
+ srcStart < src.byteLength;
+ srcStart += size, dstStart += targetStride)
+ {
+ memcpy({ src, start: srcStart, length: size }, { dst, start: dstStart });
+ }
+ }
+
+ createTestAndPipelineData(
+ state,
+ vertexCount,
+ instanceCount)
+ {
+ // Gather the test data and some additional test state for attribs.
+ return mapStateAttribs(state, (buffer, attrib) => {
+ const maxCount = buffer.stepMode === 'instance' ? instanceCount : vertexCount;
+ const formatInfo = kVertexFormatInfo[attrib.format];
+
+ let testData = this.generateTestData(attrib.format);
+ testData = this.expandTestData(testData, maxCount, formatInfo.componentCount);
+
+ return {
+ ...testData,
+ ...attrib
+ };
+ });
+ }
+
+ createExpectedBG(state, pipeline) {
+ // Create the bindgroups from that test data
+ const bgEntries = [];
+
+ for (const buffer of state) {
+ for (const attrib of buffer.attributes) {
+ const expectedDataBuffer = this.makeBufferWithContents(
+ new Uint8Array(attrib.expectedData),
+ GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE
+ );
+ bgEntries.push({
+ binding: attrib.shaderLocation,
+ resource: { buffer: expectedDataBuffer }
+ });
+ }
+ }
+
+ return this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: bgEntries
+ });
+ }
+
+ createVertexBuffers(
+ state,
+ vertexCount,
+ instanceCount)
+ {
+ // Create the vertex buffers
+ const vertexBuffers = [];
+
+ for (const buffer of state) {
+ const maxCount = buffer.stepMode === 'instance' ? instanceCount : vertexCount;
+
+ // Fill the vertex data with garbage so that we don't get `0` (which could be a test value)
+ // if the vertex shader loads the vertex data incorrectly.
+ const vertexData = new ArrayBuffer(
+ align(buffer.arrayStride * maxCount + (buffer.vbOffset ?? 0), 4)
+ );
+ new Uint8Array(vertexData).fill(0xc4);
+
+ for (const attrib of buffer.attributes) {
+ const formatInfo = kVertexFormatInfo[attrib.format];
+ this.interleaveVertexDataInto(vertexData, attrib.vertexData, {
+ targetStride: buffer.arrayStride,
+ offset: (buffer.vbOffset ?? 0) + attrib.offset,
+ size: formatInfo.byteSize
+ });
+ }
+
+ vertexBuffers.push({
+ slot: buffer.slot,
+ buffer: this.makeBufferWithContents(new Uint8Array(vertexData), GPUBufferUsage.VERTEX),
+ vbOffset: buffer.vbOffset,
+ attributes: []
+ });
+ }
+
+ return vertexBuffers;
+ }
+
+ runTest(
+ buffers,
+ // Default to using 20 vertices and 20 instances so that we cover each of the test data at least
+ // once (at the time of writing the largest testData has 16 values).
+ vertexCount = 20,
+ instanceCount = 20)
+ {
+ const testData = this.createTestAndPipelineData(buffers, vertexCount, instanceCount);
+ const pipeline = this.makeTestPipeline(testData, vertexCount, instanceCount);
+ const expectedDataBG = this.createExpectedBG(testData, pipeline);
+ const vertexBuffers = this.createVertexBuffers(testData, vertexCount, instanceCount);
+ this.submitRenderPass(pipeline, vertexBuffers, expectedDataBG, vertexCount, instanceCount);
+ }
+}
+
+export const g = makeTestGroup(VertexStateTest);
+
+g.test('vertex_format_to_shader_format_conversion').
+desc(
+ `Test that the raw data passed in vertex buffers is correctly converted to the input type in the shader. Test for:
+ - all formats
+ - 1 to 4 components in the shader's input type (unused components are filled with 0 and except the 4th with 1)
+ - various locations
+ - various slots`
+).
+params((u) =>
+u //
+.combine('format', kVertexFormats).
+combine('shaderComponentCount', [1, 2, 3, 4]).
+beginSubcases().
+combine('slotVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('shaderLocationVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+)
+).
+fn((t) => {
+ const { format, shaderComponentCount, slotVariant, shaderLocationVariant } = t.params;
+ const slot = t.makeLimitVariant('maxVertexBuffers', slotVariant);
+ const shaderLocation = t.makeLimitVariant('maxVertexAttributes', shaderLocationVariant);
+ t.runTest([
+ {
+ slot,
+ arrayStride: 16,
+ stepMode: 'vertex',
+ attributes: [
+ {
+ shaderLocation,
+ format,
+ offset: 0,
+ shaderComponentCount
+ }]
+
+ }]
+ );
+});
+
+g.test('setVertexBuffer_offset_and_attribute_offset').
+desc(
+ `Test that the vertex buffer offset and attribute offset in the vertex state are applied correctly. Test for:
+ - all formats
+ - various setVertexBuffer offsets
+ - various attribute offsets in a fixed arrayStride`
+).
+params((u) =>
+u //
+.combine('format', kVertexFormats).
+beginSubcases().
+combine('vbOffset', [0, 4, 400, 1004]).
+combine('arrayStride', [128]).
+expand('offset', (p) => {
+ const formatInfo = kVertexFormatInfo[p.format];
+ const formatSize = formatInfo.byteSize;
+ return new Set([
+ 0,
+ 4,
+ 8,
+ formatSize,
+ formatSize * 2,
+ p.arrayStride / 2,
+ p.arrayStride - formatSize - 4,
+ p.arrayStride - formatSize - 8,
+ p.arrayStride - formatSize - formatSize,
+ p.arrayStride - formatSize - formatSize * 2,
+ p.arrayStride - formatSize]
+ );
+})
+).
+fn((t) => {
+ const { format, vbOffset, arrayStride, offset } = t.params;
+ t.runTest([
+ {
+ slot: 0,
+ arrayStride,
+ stepMode: 'vertex',
+ vbOffset,
+ attributes: [
+ {
+ shaderLocation: 0,
+ format,
+ offset
+ }]
+
+ }]
+ );
+});
+
+g.test('non_zero_array_stride_and_attribute_offset').
+desc(
+ `Test that the array stride and attribute offset in the vertex state are applied correctly. Test for:
+ - all formats
+ - various array strides
+ - various attribute offsets in a fixed arrayStride`
+).
+params((u) =>
+u //
+.combine('format', kVertexFormats).
+beginSubcases().
+expand('arrayStrideVariant', (p) => {
+ const formatInfo = kVertexFormatInfo[p.format];
+ const formatSize = formatInfo.byteSize;
+
+ return [
+ { mult: 0, add: align(formatSize, 4) },
+ { mult: 0, add: align(formatSize, 4) + 4 },
+ { mult: 1, add: 0 }];
+
+}).
+expand('offsetVariant', function* (p) {
+ const formatInfo = kVertexFormatInfo[p.format];
+ const formatSize = formatInfo.byteSize;
+ yield { mult: 0, add: 0 };
+ yield { mult: 0, add: 4 };
+ if (formatSize !== 4) yield { mult: 0, add: formatSize };
+ yield { mult: 0.5, add: 0 };
+ yield { mult: 1, add: -formatSize * 2 };
+ if (formatSize !== 4) yield { mult: 1, add: -formatSize - 4 };
+ yield { mult: 1, add: -formatSize };
+})
+).
+fn((t) => {
+ const { format, arrayStrideVariant, offsetVariant } = t.params;
+ const arrayStride = t.makeLimitVariant('maxVertexBufferArrayStride', arrayStrideVariant);
+ const formatInfo = kVertexFormatInfo[format];
+ const formatSize = formatInfo.byteSize;
+ const offset = clamp(makeValueTestVariant(arrayStride, offsetVariant), {
+ min: 0,
+ max: arrayStride - formatSize
+ });
+
+ t.runTest([
+ {
+ slot: 0,
+ arrayStride,
+ stepMode: 'vertex',
+ attributes: [
+ {
+ shaderLocation: 0,
+ format,
+ offset
+ }]
+
+ }]
+ );
+});
+
+g.test('buffers_with_varying_step_mode').
+desc(
+ `Test buffers with varying step modes in the same vertex state.
+ - Various combination of step modes`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('stepModes', [
+['instance'],
+['vertex', 'vertex', 'instance'],
+['instance', 'vertex', 'instance'],
+['vertex', 'instance', 'vertex', 'vertex']]
+)
+).
+fn((t) => {
+ const { stepModes } = t.params;
+ const state = stepModes.map((stepMode, i) => ({
+ slot: i,
+ arrayStride: 4,
+ stepMode,
+ attributes: [
+ {
+ shaderLocation: i,
+ format: 'float32',
+ offset: 0
+ }]
+
+ }));
+ t.runTest(state);
+});
+
+g.test('vertex_buffer_used_multiple_times_overlapped').
+desc(
+ `Test using the same vertex buffer in for multiple "vertex buffers", with data from each buffer overlapping.
+ - For each vertex format.
+ - For various numbers of vertex buffers [2, 3, max]`
+).
+params((u) =>
+u //
+.combine('format', kVertexFormats).
+beginSubcases().
+combine('vbCountVariant', [
+{ mult: 0, add: 2 },
+{ mult: 0, add: 3 },
+{ mult: 1, add: 0 }]
+).
+combine('additionalVBOffset', [0, 4, 120])
+).
+fn((t) => {
+ const { format, vbCountVariant, additionalVBOffset } = t.params;
+ const vbCount = t.makeLimitVariant('maxVertexBuffers', vbCountVariant);
+ const kVertexCount = 20;
+ const kInstanceCount = 1;
+ const formatInfo = kVertexFormatInfo[format];
+ const formatByteSize = formatInfo.byteSize;
+ // We need to align so the offset for non-0 setVertexBuffer don't fail validation.
+ const alignedFormatByteSize = align(formatByteSize, 4);
+
+ // In this test we want to test using the same vertex buffer for multiple different attributes.
+ // For example if vbCount is 3, we will create a vertex buffer containing the following data:
+ // a0, a1, a2, a3, ..., a<baseDataVertexCount>
+ // We also create the expected data for the vertex fetching from that buffer so we can modify it
+ // below.
+ const baseDataVertexCount = kVertexCount + vbCount - 1;
+ const baseData = t.createTestAndPipelineData(
+ [
+ {
+ slot: 0,
+ arrayStride: alignedFormatByteSize,
+ stepMode: 'vertex',
+ vbOffset: additionalVBOffset,
+ attributes: [{ shaderLocation: 0, format, offset: 0 }]
+ }],
+
+ baseDataVertexCount,
+ kInstanceCount
+ );
+ const vertexBuffer = t.createVertexBuffers(baseData, baseDataVertexCount, kInstanceCount)[0].
+ buffer;
+
+ // We are going to bind the vertex buffer multiple times, each time at a different offset that's
+ // a multiple of the data size. So what should be fetched by the vertex shader is:
+ // - attrib0: a0, a1, ..., a19
+ // - attrib1: a1, a2, ..., a20
+ // - attrib2: a2, a3, ..., a21
+ // etc.
+ // We re-create the test data by:
+ // 1) creating multiple "vertex buffers" that all point at the GPUBuffer above but at
+ // different offsets.
+ // 2) selecting what parts of the expectedData each attribute will see in the expectedData for
+ // the full vertex buffer.
+ const baseTestData = baseData[0].attributes[0];
+ assert(baseTestData.testComponentCount === formatInfo.componentCount * baseDataVertexCount);
+ const expectedDataBytesPerVertex = baseTestData.expectedData.byteLength / baseDataVertexCount;
+
+ const testData = [];
+ const vertexBuffers = [];
+ for (let i = 0; i < vbCount; i++) {
+ vertexBuffers.push({
+ buffer: vertexBuffer,
+ slot: i,
+ vbOffset: additionalVBOffset + i * alignedFormatByteSize,
+ attributes: []
+ });
+
+ testData.push({
+ slot: i,
+ arrayStride: alignedFormatByteSize,
+ stepMode: 'vertex',
+ attributes: [
+ {
+ shaderLocation: i,
+ format,
+ offset: 0,
+
+ shaderBaseType: baseTestData.shaderBaseType,
+ floatTolerance: baseTestData.floatTolerance,
+ // Select vertices [i, i + kVertexCount]
+ testComponentCount: kVertexCount * formatInfo.componentCount,
+ expectedData: baseTestData.expectedData.slice(
+ expectedDataBytesPerVertex * i,
+ expectedDataBytesPerVertex * (kVertexCount + i)
+ ),
+ vertexData: new ArrayBuffer(0)
+ }]
+
+ });
+ }
+
+ // Run the test with the modified test data.
+ const pipeline = t.makeTestPipeline(testData, kVertexCount, kInstanceCount);
+ const expectedDataBG = t.createExpectedBG(testData, pipeline);
+ t.submitRenderPass(pipeline, vertexBuffers, expectedDataBG, kVertexCount, kInstanceCount);
+});
+
+g.test('vertex_buffer_used_multiple_times_interleaved').
+desc(
+ `Test using the same vertex buffer in for multiple "vertex buffers", with data from each buffer interleaved.
+ - For each vertex format.
+ - For various numbers of vertex buffers [2, 3, max]`
+).
+params((u) =>
+u //
+.combine('format', kVertexFormats).
+beginSubcases().
+combine('vbCountVariant', [
+{ mult: 0, add: 2 },
+{ mult: 0, add: 3 },
+{ mult: 1, add: 0 }]
+).
+combine('additionalVBOffset', [0, 4, 120])
+).
+fn((t) => {
+ const { format, vbCountVariant, additionalVBOffset } = t.params;
+ const vbCount = t.makeLimitVariant('maxVertexBuffers', vbCountVariant);
+ const kVertexCount = 20;
+ const kInstanceCount = 1;
+ const formatInfo = kVertexFormatInfo[format];
+ const formatByteSize = formatInfo.byteSize;
+ // We need to align so the offset for non-0 setVertexBuffer don't fail validation.
+ const alignedFormatByteSize = align(formatByteSize, 4);
+
+ // Create data for a single vertex buffer with many attributes, that will be split between
+ // many vertex buffers set at different offsets.
+
+ // In this test we want to test using the same vertex buffer for multiple different attributes.
+ // For example if vbCount is 3, we will create a vertex buffer containing the following data:
+ // a0, a0, a0, a1, a1, a1, ...
+ // To do that we create a single vertex buffer with `vbCount` attributes that all have the same
+ // format.
+ const attribs = [];
+ for (let i = 0; i < vbCount; i++) {
+ attribs.push({ format, offset: i * alignedFormatByteSize, shaderLocation: i });
+ }
+ const baseData = t.createTestAndPipelineData(
+ [
+ {
+ slot: 0,
+ arrayStride: alignedFormatByteSize * vbCount,
+ stepMode: 'vertex',
+ vbOffset: additionalVBOffset,
+ attributes: attribs
+ }],
+
+ // Request one vertex more than what we need so we have an extra full stride. Otherwise WebGPU
+ // validation of vertex being in bounds will fail for all vertex buffers at an offset that's
+ // not 0 (since their last stride will go beyond the data for vertex kVertexCount -1).
+ kVertexCount + 1,
+ kInstanceCount
+ );
+ const vertexBuffer = t.createVertexBuffers(baseData, kVertexCount + 1, kInstanceCount)[0].
+ buffer;
+
+ // Then we recreate test data by:
+ // 1) creating multiple "vertex buffers" that all point at the GPUBuffer above but at
+ // different offsets.
+ // 2) have multiple vertex buffer, each with one attributes that will expect a0, a1, ...
+ const testData = [];
+ const vertexBuffers = [];
+ for (let i = 0; i < vbCount; i++) {
+ vertexBuffers.push({
+ slot: i,
+ buffer: vertexBuffer,
+ vbOffset: additionalVBOffset + i * alignedFormatByteSize,
+ attributes: []
+ });
+ testData.push({
+ ...baseData[0],
+ slot: i,
+ attributes: [{ ...baseData[0].attributes[i], offset: 0 }]
+ });
+ }
+
+ // Run the test with the modified test data.
+ const pipeline = t.makeTestPipeline(testData, kVertexCount, kInstanceCount);
+ const expectedDataBG = t.createExpectedBG(testData, pipeline);
+ t.submitRenderPass(pipeline, vertexBuffers, expectedDataBG, kVertexCount, kInstanceCount);
+});
+
+g.test('max_buffers_and_attribs').
+desc(
+ `Test a vertex state that loads as many attributes and buffers as possible.
+ - For each format.
+ `
+).
+params((u) => u.combine('format', kVertexFormats)).
+fn((t) => {
+ const { format } = t.params;
+ // In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute
+ const maxVertexBuffers = t.device.limits.maxVertexBuffers;
+ const deviceMaxVertexAttributes = t.device.limits.maxVertexAttributes;
+ const maxVertexAttributes = deviceMaxVertexAttributes - (t.isCompatibility ? 2 : 0);
+ const attributesPerBuffer = Math.ceil(maxVertexAttributes / maxVertexBuffers);
+ let attributesEmitted = 0;
+
+ const state = [];
+ for (let i = 0; i < maxVertexBuffers; i++) {
+ const attributes = [];
+ for (let j = 0; j < attributesPerBuffer && attributesEmitted < maxVertexAttributes; j++) {
+ attributes.push({ format, offset: 0, shaderLocation: attributesEmitted });
+ attributesEmitted++;
+ }
+ state.push({
+ slot: i,
+ stepMode: 'vertex',
+ arrayStride: 32,
+ attributes
+ });
+ }
+ t.runTest(state);
+});
+
+g.test('array_stride_zero').
+desc(
+ `Test that arrayStride 0 correctly uses the same data for all vertex/instances, while another test vertex buffer with arrayStride != 0 gets different data.
+ - Test for all formats
+ - Test for both step modes`
+).
+params((u) =>
+u //
+.combine('format', kVertexFormats).
+beginSubcases().
+combine('stepMode', ['vertex', 'instance']).
+expand('offsetVariant', (p) => {
+ const formatInfo = kVertexFormatInfo[p.format];
+ const formatSize = formatInfo.byteSize;
+ return filterUniqueValueTestVariants([
+ { mult: 0, add: 0 },
+ { mult: 0, add: 4 },
+ { mult: 0, add: 8 },
+ { mult: 0, add: formatSize },
+ { mult: 0, add: formatSize * 2 },
+ { mult: 0.5, add: 0 },
+ { mult: 1, add: -formatSize - 4 },
+ { mult: 1, add: -formatSize - 8 },
+ { mult: 1, add: -formatSize },
+ { mult: 1, add: -formatSize * 2 }]
+ );
+})
+).
+fn((t) => {
+ const { format, stepMode, offsetVariant } = t.params;
+ const offset = t.makeLimitVariant('maxVertexBufferArrayStride', offsetVariant);
+ const kCount = 10;
+
+ // Create the stride 0 part of the test, first by faking a single vertex being drawn and
+ // then expanding the data to cover kCount vertex / instances
+ const stride0TestData = t.createTestAndPipelineData(
+ [
+ {
+ slot: 0,
+ arrayStride: 2048,
+ stepMode,
+ vbOffset: offset, // used to push data in the vertex buffer
+ attributes: [{ format, offset: 0, shaderLocation: 0 }]
+ }],
+
+ 1,
+ 1
+ )[0];
+ const stride0VertexBuffer = t.createVertexBuffers([stride0TestData], kCount, kCount)[0];
+
+ // Expand the stride0 test data to have kCount values for expectedData.
+ const originalData = stride0TestData.attributes[0].expectedData;
+ const expandedData = new ArrayBuffer(kCount * originalData.byteLength);
+ for (let i = 0; i < kCount; i++) {
+ new Uint8Array(expandedData, originalData.byteLength * i).set(new Uint8Array(originalData));
+ }
+
+ // Fixup stride0TestData to use arrayStride 0.
+ stride0TestData.attributes[0].offset = offset;
+ stride0TestData.attributes[0].expectedData = expandedData;
+ stride0TestData.attributes[0].testComponentCount *= kCount;
+ stride0TestData.arrayStride = 0;
+ stride0VertexBuffer.vbOffset = 0;
+
+ // Create the part of the state that will be varying for each vertex / instance
+ const varyingTestData = t.createTestAndPipelineData(
+ [
+ {
+ slot: 1,
+ arrayStride: 32,
+ stepMode,
+ attributes: [{ format, offset: 0, shaderLocation: 1 }]
+ }],
+
+ kCount,
+ kCount
+ )[0];
+ const varyingVertexBuffer = t.createVertexBuffers([varyingTestData], kCount, kCount)[0];
+
+ // Run the test with the merged test state.
+ const state = [stride0TestData, varyingTestData];
+ const vertexBuffers = [stride0VertexBuffer, varyingVertexBuffer];
+
+ const pipeline = t.makeTestPipeline(state, kCount, kCount);
+ const expectedDataBG = t.createExpectedBG(state, pipeline);
+ t.submitRenderPass(pipeline, vertexBuffers, expectedDataBG, kCount, kCount);
+});
+
+g.test('discontiguous_location_and_attribs').
+desc('Test that using far away slots / shaderLocations works as expected').
+fn((t) => {
+ t.runTest([
+ {
+ slot: t.device.limits.maxVertexBuffers - 1,
+ arrayStride: 4,
+ stepMode: 'vertex',
+ attributes: [
+ { format: 'uint8x2', offset: 2, shaderLocation: 0 },
+ { format: 'uint8x2', offset: 0, shaderLocation: 8 }]
+
+ },
+ {
+ slot: 1,
+ arrayStride: 16,
+ stepMode: 'instance',
+ vbOffset: 1000,
+ attributes: [
+ {
+ format: 'uint32x4',
+ offset: 0,
+ shaderLocation: t.device.limits.maxVertexAttributes - 1
+ }]
+
+ }]
+ );
+});
+
+g.test('overlapping_attributes').
+desc(
+ `Test that overlapping attributes in the same vertex buffer works
+ - Test for all formats`
+).
+params((u) => u.combine('format', kVertexFormats)).
+fn((t) => {
+ const { format } = t.params;
+
+ // In compat mode, @builtin(vertex_index) and @builtin(instance_index) each take an attribute
+ const maxVertexAttributes = t.device.limits.maxVertexAttributes - (t.isCompatibility ? 2 : 0);
+ const attributes = [];
+ for (let i = 0; i < maxVertexAttributes; i++) {
+ attributes.push({ format, offset: 0, shaderLocation: i });
+ }
+
+ t.runTest([
+ {
+ slot: 0,
+ stepMode: 'vertex',
+ arrayStride: 32,
+ attributes
+ }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/index_format.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/index_format.spec.js
new file mode 100644
index 0000000000..60ffb5bc36
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/operation/vertex_state/index_format.spec.js
@@ -0,0 +1,584 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test indexing, index format and primitive restart.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { getTextureCopyLayout } from '../../../util/texture/layout.js';
+
+const kHeight = 4;
+const kWidth = 8;
+const kTextureFormat = 'r8uint';
+
+/** 4x4 grid of r8uint values (each 0 or 1). */
+
+
+
+
+
+
+
+/** Expected 4x4 rasterization of a bottom-left triangle. */
+const kBottomLeftTriangle = [
+[0, 0, 0, 0, 0, 0, 0, 0],
+[0, 0, 0, 0, 1, 0, 0, 0],
+[0, 0, 0, 0, 1, 1, 0, 0],
+[0, 0, 0, 0, 1, 1, 1, 0]];
+
+
+/** Expected 4x4 rasterization filling the whole quad. */
+const kSquare = [
+[0, 0, 0, 0, 1, 1, 1, 1],
+[0, 0, 0, 0, 1, 1, 1, 1],
+[0, 0, 0, 0, 1, 1, 1, 1],
+[0, 0, 0, 0, 1, 1, 1, 1]];
+
+
+/** Expected 4x4 rasterization with no pixels. */
+const kNothing = [
+[0, 0, 0, 0, 0, 0, 0, 0],
+[0, 0, 0, 0, 0, 0, 0, 0],
+[0, 0, 0, 0, 0, 0, 0, 0],
+[0, 0, 0, 0, 0, 0, 0, 0]];
+
+
+const { byteLength, bytesPerRow, rowsPerImage } = getTextureCopyLayout(kTextureFormat, '2d', [
+kWidth,
+kHeight,
+1]
+);
+
+class IndexFormatTest extends GPUTest {
+ MakeRenderPipeline(
+ topology,
+ stripIndexFormat)
+ {
+ const vertexModule = this.device.createShaderModule({
+ // NOTE: These positions will create triangles that cut right through pixel centers. If this
+ // results in different rasterization results on different hardware, tweak to avoid this.
+ code: `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32)
+ -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 4>(
+ vec2<f32>(0.01, 0.98),
+ vec2<f32>(0.99, -0.98),
+ vec2<f32>(0.99, 0.98),
+ vec2<f32>(0.01, -0.98));
+
+ if (VertexIndex == 0xFFFFu || VertexIndex == 0xFFFFFFFFu) {
+ return vec4<f32>(-0.99, -0.98, 0.0, 1.0);
+ }
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+ `
+ });
+
+ const fragmentModule = this.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) u32 {
+ return 1u;
+ }
+ `
+ });
+
+ return this.device.createRenderPipeline({
+ layout: this.device.createPipelineLayout({ bindGroupLayouts: [] }),
+ vertex: { module: vertexModule, entryPoint: 'main' },
+ fragment: {
+ module: fragmentModule,
+ entryPoint: 'main',
+ targets: [{ format: kTextureFormat }]
+ },
+ primitive: {
+ topology,
+ stripIndexFormat
+ }
+ });
+ }
+
+ CreateIndexBuffer(indices, indexFormat) {
+ const typedArrayConstructor = { uint16: Uint16Array, uint32: Uint32Array }[indexFormat];
+ return this.makeBufferWithContents(new typedArrayConstructor(indices), GPUBufferUsage.INDEX);
+ }
+
+ run(
+ indexBuffer,
+ indexCount,
+ indexFormat,
+ indexOffset = 0,
+ primitiveTopology = 'triangle-list')
+ {
+ let pipeline;
+ // The indexFormat must be set in render pipeline descriptor that specifies a strip primitive
+ // topology for primitive restart testing
+ if (primitiveTopology === 'line-strip' || primitiveTopology === 'triangle-strip') {
+ pipeline = this.MakeRenderPipeline(primitiveTopology, indexFormat);
+ } else {
+ pipeline = this.MakeRenderPipeline(primitiveTopology);
+ }
+
+ const colorAttachment = this.device.createTexture({
+ format: kTextureFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const result = this.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.setIndexBuffer(indexBuffer, indexFormat, indexOffset);
+ pass.drawIndexed(indexCount);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment },
+ { buffer: result, bytesPerRow, rowsPerImage },
+ [kWidth, kHeight]
+ );
+ this.device.queue.submit([encoder.finish()]);
+
+ return result;
+ }
+
+ CreateExpectedUint8Array(renderShape) {
+ const arrayBuffer = new Uint8Array(byteLength);
+ for (let row = 0; row < renderShape.length; row++) {
+ for (let col = 0; col < renderShape[row].length; col++) {
+ const texel = renderShape[row][col];
+
+ const kBytesPerTexel = 1; // r8uint
+ const byteOffset = row * bytesPerRow + col * kBytesPerTexel;
+ arrayBuffer[byteOffset] = texel;
+ }
+ }
+ return arrayBuffer;
+ }
+}
+
+export const g = makeTestGroup(IndexFormatTest);
+
+g.test('index_format,uint16').
+desc('Test rendering result of indexed draw with index format of uint16.').
+paramsSubcasesOnly([
+{ indexOffset: 0, _indexCount: 10, _expectedShape: kSquare },
+{ indexOffset: 6, _indexCount: 6, _expectedShape: kBottomLeftTriangle },
+{ indexOffset: 18, _indexCount: 0, _expectedShape: kNothing }]
+).
+fn((t) => {
+ const { indexOffset, _indexCount, _expectedShape } = t.params;
+
+ // If this is written as uint16 but interpreted as uint32, it will have index 1 and 2 be both 0
+ // and render nothing.
+ // And the index buffer size - offset must be not less than the size required by triangle
+ // list, otherwise it also render nothing.
+ const indices = [1, 2, 0, 0, 0, 0, 0, 1, 3, 0];
+ const indexBuffer = t.CreateIndexBuffer(indices, 'uint16');
+ const result = t.run(indexBuffer, _indexCount, 'uint16', indexOffset);
+
+ const expectedTextureValues = t.CreateExpectedUint8Array(_expectedShape);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+});
+
+g.test('index_format,uint32').
+desc('Test rendering result of indexed draw with index format of uint32.').
+paramsSubcasesOnly([
+{ indexOffset: 0, _indexCount: 10, _expectedShape: kSquare },
+{ indexOffset: 12, _indexCount: 7, _expectedShape: kBottomLeftTriangle },
+{ indexOffset: 36, _indexCount: 0, _expectedShape: kNothing }]
+).
+fn((t) => {
+ const { indexOffset, _indexCount, _expectedShape } = t.params;
+
+ // If this is interpreted as uint16, then it would be 0, 1, 0, ... and would draw nothing.
+ // And the index buffer size - offset must be not less than the size required by triangle
+ // list, otherwise it also render nothing.
+ const indices = [1, 2, 0, 0, 0, 0, 0, 1, 3, 0];
+ const indexBuffer = t.CreateIndexBuffer(indices, 'uint32');
+ const result = t.run(indexBuffer, _indexCount, 'uint32', indexOffset);
+
+ const expectedTextureValues = t.CreateExpectedUint8Array(_expectedShape);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+});
+
+g.test('index_format,change_pipeline_after_setIndexBuffer').
+desc('Test that setting the index buffer before the pipeline works correctly.').
+params((u) => u.combine('setPipelineBeforeSetIndexBuffer', [false, true])).
+fn((t) => {
+ const indexOffset = 12;
+ const indexCount = 7;
+ const expectedShape = kBottomLeftTriangle;
+
+ const indexFormat16 = 'uint16';
+ const indexFormat32 = 'uint32';
+
+ const indices = [1, 2, 0, 0, 0, 0, 0, 1, 3, 0];
+ const indexBuffer = t.CreateIndexBuffer(indices, indexFormat32);
+
+ const kPrimitiveTopology = 'triangle-strip';
+ const pipeline32 = t.MakeRenderPipeline(kPrimitiveTopology, indexFormat32);
+ const pipeline16 = t.MakeRenderPipeline(kPrimitiveTopology, indexFormat16);
+
+ const colorAttachment = t.device.createTexture({
+ format: kTextureFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const result = t.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ if (t.params.setPipelineBeforeSetIndexBuffer) {
+ pass.setPipeline(pipeline16);
+ }
+ pass.setIndexBuffer(indexBuffer, indexFormat32, indexOffset);
+ pass.setPipeline(pipeline32); // Set the pipeline for 'indexFormat32' again.
+ pass.drawIndexed(indexCount);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment },
+ { buffer: result, bytesPerRow, rowsPerImage },
+ [kWidth, kHeight]
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ const expectedTextureValues = t.CreateExpectedUint8Array(expectedShape);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+});
+
+g.test('index_format,setIndexBuffer_before_setPipeline').
+desc('Test that setting the index buffer before the pipeline works correctly.').
+params((u) => u.combine('setIndexBufferBeforeSetPipeline', [false, true])).
+fn((t) => {
+ const indexOffset = 12;
+ const indexCount = 7;
+ const expectedShape = kBottomLeftTriangle;
+
+ const indexFormat = 'uint32';
+
+ const indices = [1, 2, 0, 0, 0, 0, 0, 1, 3, 0];
+ const indexBuffer = t.CreateIndexBuffer(indices, indexFormat);
+
+ const kPrimitiveTopology = 'triangle-strip';
+ const pipeline = t.MakeRenderPipeline(kPrimitiveTopology, indexFormat);
+
+ const colorAttachment = t.device.createTexture({
+ format: kTextureFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const result = t.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ if (t.params.setIndexBufferBeforeSetPipeline) {
+ pass.setIndexBuffer(indexBuffer, indexFormat, indexOffset);
+ pass.setPipeline(pipeline);
+ } else {
+ pass.setPipeline(pipeline);
+ pass.setIndexBuffer(indexBuffer, indexFormat, indexOffset);
+ }
+
+ pass.drawIndexed(indexCount);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment },
+ { buffer: result, bytesPerRow, rowsPerImage },
+ [kWidth, kHeight]
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ const expectedTextureValues = t.CreateExpectedUint8Array(expectedShape);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+});
+
+g.test('index_format,setIndexBuffer_different_formats').
+desc(
+ `
+ Test that index buffers of multiple formats can be used with a pipeline that doesn't use strip
+ primitive topology.
+ `
+).
+fn((t) => {
+ const indices = [1, 2, 0, 0, 0, 0, 0, 1, 3, 0];
+
+ // Create a pipeline to be used by different index formats.
+ const kPrimitiveTopology = 'triangle-list';
+ const pipeline = t.MakeRenderPipeline(kPrimitiveTopology);
+
+ const expectedTextureValues = t.CreateExpectedUint8Array(kBottomLeftTriangle);
+
+ const colorAttachment = t.device.createTexture({
+ format: kTextureFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const result = t.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ let encoder = t.device.createCommandEncoder();
+ {
+ const indexFormat = 'uint32';
+ const indexOffset = 12;
+ const indexCount = 7;
+ const indexBuffer = t.CreateIndexBuffer(indices, indexFormat);
+
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ pass.setIndexBuffer(indexBuffer, indexFormat, indexOffset);
+ pass.setPipeline(pipeline);
+ pass.drawIndexed(indexCount);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment },
+ { buffer: result, bytesPerRow, rowsPerImage },
+ [kWidth, kHeight]
+ );
+ }
+ t.device.queue.submit([encoder.finish()]);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+
+ // Call setIndexBuffer with the pipeline and a different index format buffer.
+ encoder = t.device.createCommandEncoder();
+ {
+ const indexFormat = 'uint16';
+ const indexOffset = 6;
+ const indexCount = 6;
+ const indexBuffer = t.CreateIndexBuffer(indices, indexFormat);
+
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ pass.setIndexBuffer(indexBuffer, indexFormat, indexOffset);
+ pass.setPipeline(pipeline);
+ pass.drawIndexed(indexCount);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: colorAttachment },
+ { buffer: result, bytesPerRow, rowsPerImage },
+ [kWidth, kHeight]
+ );
+ }
+ t.device.queue.submit([encoder.finish()]);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+});
+
+g.test('primitive_restart').
+desc(
+ `
+Test primitive restart with each primitive topology.
+
+Primitive restart should be always active with strip primitive topologies
+('line-strip' or 'triangle-strip') and never active for other topologies, where
+the primitive restart value isn't special and should be treated as a regular index value.
+
+The value -1 gets uploaded as 0xFFFF or 0xFFFF_FFFF according to the format.
+
+The positions of these points are embedded in the shader above, and look like this:
+ | 0 2|
+ | |
+ -1 3 1|
+
+Below are the indices lists used for each test, and the expected rendering result of each
+(approximately, in the case of incorrect results). This shows the expected result (marked '->')
+is different from what you would get if the topology were incorrect.
+
+- primitiveTopology: triangle-list
+ indices: [0, 1, 3, -1, 2, 1, 0, 0],
+ -> triangle-list: (0, 1, 3), (-1, 2, 1)
+ | # #|
+ | ####|
+ | #####|
+ | #######|
+ triangle-list with restart: (0, 1, 3), (2, 1, 0)
+ triangle-strip: (0, 1, 3), (2, 1, 0), (1, 0, 0)
+ | ####|
+ | ####|
+ | ####|
+ | ####|
+ triangle-strip w/o restart: (0, 1, 3), (1, 3, -1), (3, -1, 2), (-1, 2, 1), (2, 1, 0), (1, 0, 0)
+ | ####|
+ | ####|
+ | #####|
+ | #######|
+
+- primitiveTopology: triangle-strip
+ indices: [3, 1, 0, -1, 2, 2, 1, 3],
+ -> triangle-strip: (3, 1, 0), (2, 2, 1), (2, 1, 3)
+ | # #|
+ | ####|
+ | ####|
+ | ####|
+ triangle-strip w/o restart: (3, 1, 0), (1, 0, -1), (0, -1, 2), (2, 2, 1), (2, 3, 1)
+ | ####|
+ | #####|
+ | ######|
+ | #######|
+ triangle-list: (3, 1, 0), (-1, 2, 2)
+ triangle-list with restart: (3, 1, 0), (2, 2, 1)
+ | |
+ | # |
+ | ## |
+ | ### |
+
+- primitiveTopology: point, line-list, line-strip:
+ indices: [0, 1, -1, 2, -1, 2, 3, 0],
+ -> point-list: (0), (1), (-1), (2), (3), (0)
+ | # #|
+ | |
+ | |
+ |# # #|
+ point-list with restart (0), (1), (2), (3), (0)
+ | # #|
+ | |
+ | |
+ | # #|
+ -> line-list: (0, 1), (-1, 2), (3, 0)
+ | # ##|
+ | ## |
+ | ### # |
+ |## # #|
+ line-list with restart: (0, 1), (2, 3)
+ | # #|
+ | ## |
+ | ## |
+ | # #|
+ -> line-strip: (0, 1), (2, 3), (3, 0)
+ | # #|
+ | ### |
+ | ### |
+ | # #|
+ line-strip w/o restart: (0, 1), (1, -1), (-1, 2), (2, 3), (3, 3)
+ | # ##|
+ | ### |
+ | ## ## |
+ |########|
+`
+).
+params((u) =>
+u //
+.combine('indexFormat', ['uint16', 'uint32']).
+combineWithParams([
+{
+ primitiveTopology: 'point-list',
+ _indices: [0, 1, -1, 2, 3, 0],
+ _expectedShape: [
+ [0, 0, 0, 0, 1, 0, 0, 1],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [1, 0, 0, 0, 1, 0, 0, 1]]
+
+},
+{
+ primitiveTopology: 'line-list',
+ _indices: [0, 1, -1, 2, 3, 0],
+ _expectedShape: [
+ [0, 0, 0, 0, 1, 0, 1, 1],
+ [0, 0, 0, 0, 1, 1, 0, 0],
+ [0, 0, 1, 1, 1, 0, 1, 0],
+ [1, 1, 0, 0, 1, 0, 0, 1]]
+
+},
+{
+ primitiveTopology: 'line-strip',
+ _indices: [0, 1, -1, 2, 3, 0],
+ _expectedShape: [
+ [0, 0, 0, 0, 1, 0, 0, 1],
+ [0, 0, 0, 0, 1, 1, 1, 0],
+ [0, 0, 0, 0, 1, 1, 1, 0],
+ [0, 0, 0, 0, 1, 0, 0, 1]]
+
+},
+{
+ primitiveTopology: 'triangle-list',
+ _indices: [0, 1, 3, -1, 2, 1, 0, 0],
+ _expectedShape: [
+ [0, 0, 0, 0, 0, 0, 0, 1],
+ [0, 0, 0, 0, 1, 1, 1, 1],
+ [0, 0, 0, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1, 1]]
+
+},
+{
+ primitiveTopology: 'triangle-strip',
+ _indices: [3, 1, 0, -1, 2, 2, 1, 3],
+ _expectedShape: [
+ [0, 0, 0, 0, 0, 0, 0, 1],
+ [0, 0, 0, 0, 1, 0, 1, 1],
+ [0, 0, 0, 0, 1, 1, 1, 1],
+ [0, 0, 0, 0, 1, 1, 1, 1]]
+
+}]
+)
+).
+fn((t) => {
+ const { indexFormat, primitiveTopology, _indices, _expectedShape } = t.params;
+
+ const indexBuffer = t.CreateIndexBuffer(_indices, indexFormat);
+ const result = t.run(indexBuffer, _indices.length, indexFormat, 0, primitiveTopology);
+
+ const expectedTextureValues = t.CreateExpectedUint8Array(_expectedShape);
+ t.expectGPUBufferValuesEqual(result, expectedTextureValues);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/create.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/create.spec.js
new file mode 100644
index 0000000000..d28cee735a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/create.spec.js
@@ -0,0 +1,113 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for validation in createBuffer.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import {
+ kAllBufferUsageBits,
+ kBufferSizeAlignment,
+ kBufferUsages } from
+'../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import { kMaxSafeMultipleOf8 } from '../../../util/math.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+assert(kBufferSizeAlignment === 4);
+g.test('size').
+desc(
+ 'Test buffer size alignment is validated to be a multiple of 4 if mappedAtCreation is true.'
+).
+params((u) =>
+u.
+combine('mappedAtCreation', [false, true]).
+beginSubcases().
+combine('size', [
+0,
+kBufferSizeAlignment * 0.5,
+kBufferSizeAlignment,
+kBufferSizeAlignment * 1.5,
+kBufferSizeAlignment * 2]
+)
+).
+fn((t) => {
+ const { mappedAtCreation, size } = t.params;
+ const isValid = !mappedAtCreation || size % kBufferSizeAlignment === 0;
+ const usage = BufferUsage.COPY_SRC;
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createBuffer({ size, usage, mappedAtCreation }),
+ !isValid
+ );
+});
+
+g.test('limit').
+desc('Test buffer size is validated against maxBufferSize.').
+params((u) => u.beginSubcases().combine('sizeAddition', [-1, 0, +1])).
+fn((t) => {
+ const { sizeAddition } = t.params;
+ const size = t.makeLimitVariant('maxBufferSize', { mult: 1, add: sizeAddition });
+ const isValid = size <= t.device.limits.maxBufferSize;
+ const usage = BufferUsage.COPY_SRC;
+ t.expectGPUError('validation', () => t.device.createBuffer({ size, usage }), !isValid);
+});
+
+const kInvalidUsage = 0x8000;
+assert((kInvalidUsage & kAllBufferUsageBits) === 0);
+g.test('usage').
+desc('Test combinations of zero to two usage flags are validated to be valid.').
+params((u) =>
+u.
+combine('usage1', [0, ...kBufferUsages, kInvalidUsage]).
+combine('usage2', [0, ...kBufferUsages, kInvalidUsage]).
+beginSubcases().
+combine('mappedAtCreation', [false, true])
+).
+fn((t) => {
+ const { mappedAtCreation, usage1, usage2 } = t.params;
+ const usage = usage1 | usage2;
+
+ const isValid =
+ usage !== 0 &&
+ (usage & ~kAllBufferUsageBits) === 0 && (
+ (usage & GPUBufferUsage.MAP_READ) === 0 ||
+ (usage & ~(GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ)) === 0) && (
+ (usage & GPUBufferUsage.MAP_WRITE) === 0 ||
+ (usage & ~(GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE)) === 0);
+
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createBuffer({ size: kBufferSizeAlignment * 2, usage, mappedAtCreation }),
+ !isValid
+ );
+});
+
+const BufferUsage = GPUConst.BufferUsage;
+
+g.test('createBuffer_invalid_and_oom').
+desc(
+ `When creating a mappable buffer, it's expected that shmem may be immediately allocated
+(in the content process, before validation occurs in the GPU process). If the buffer is really
+large, though, it could fail shmem allocation before validation fails. Ensure that OOM error is
+hidden behind the "more severe" validation error.`
+).
+paramsSubcasesOnly((u) =>
+u.combineWithParams([
+{ _valid: true, usage: BufferUsage.UNIFORM, size: 16 },
+{ _valid: true, usage: BufferUsage.STORAGE, size: 16 },
+// Invalid because UNIFORM is not allowed with map usages.
+{ usage: BufferUsage.MAP_WRITE | BufferUsage.UNIFORM, size: 16 },
+{ usage: BufferUsage.MAP_WRITE | BufferUsage.UNIFORM, size: kMaxSafeMultipleOf8 },
+{ usage: BufferUsage.MAP_WRITE | BufferUsage.UNIFORM, size: 0x20_0000_0000 }, // 128 GiB
+{ usage: BufferUsage.MAP_READ | BufferUsage.UNIFORM, size: 16 },
+{ usage: BufferUsage.MAP_READ | BufferUsage.UNIFORM, size: kMaxSafeMultipleOf8 },
+{ usage: BufferUsage.MAP_READ | BufferUsage.UNIFORM, size: 0x20_0000_0000 } // 128 GiB
+])
+).
+fn((t) => {
+ const { _valid, usage, size } = t.params;
+
+ t.expectGPUError('validation', () => t.device.createBuffer({ size, usage }), !_valid);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/destroy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/destroy.spec.js
new file mode 100644
index 0000000000..59b6c9500d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/destroy.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for GPUBuffer.destroy.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kBufferUsages } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('all_usages').
+desc('Test destroying buffers of every usage type.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('usage', kBufferUsages)
+).
+fn((t) => {
+ const { usage } = t.params;
+ const buf = t.device.createBuffer({
+ size: 4,
+ usage
+ });
+
+ buf.destroy();
+});
+
+g.test('error_buffer').
+desc('Test that error buffers may be destroyed without generating validation errors.').
+fn((t) => {
+ const buf = t.getErrorBuffer();
+ buf.destroy();
+});
+
+g.test('twice').
+desc(
+ `Test that destroying a buffer more than once is allowed.
+ - Tests buffers which are mapped at creation or not
+ - Tests buffers with various usages`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('mappedAtCreation', [false, true]).
+combineWithParams([
+{ size: 4, usage: GPUConst.BufferUsage.COPY_SRC },
+{ size: 4, usage: GPUConst.BufferUsage.MAP_WRITE | GPUConst.BufferUsage.COPY_SRC },
+{ size: 4, usage: GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.MAP_READ }]
+)
+).
+fn((t) => {
+ const buf = t.device.createBuffer(t.params);
+
+ buf.destroy();
+ buf.destroy();
+});
+
+g.test('while_mapped').
+desc(
+ `Test destroying buffers while mapped or after being unmapped.
+ - Tests {mappable, unmappable mapAtCreation, mappable mapAtCreation}
+ - Tests while {mapped, mapped at creation, unmapped}`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('mappedAtCreation', [false, true]).
+combine('unmapBeforeDestroy', [false, true]).
+combineWithParams([
+{ usage: GPUConst.BufferUsage.COPY_SRC },
+{ usage: GPUConst.BufferUsage.MAP_WRITE | GPUConst.BufferUsage.COPY_SRC },
+{ usage: GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.MAP_READ },
+{
+ usage: GPUConst.BufferUsage.MAP_WRITE | GPUConst.BufferUsage.COPY_SRC,
+ mapMode: GPUConst.MapMode.WRITE
+},
+{
+ usage: GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.MAP_READ,
+ mapMode: GPUConst.MapMode.READ
+}]
+).
+unless((p) => p.mappedAtCreation === false && p.mapMode === undefined)
+).
+fn(async (t) => {
+ const { usage, mapMode, mappedAtCreation, unmapBeforeDestroy } = t.params;
+ const buf = t.device.createBuffer({
+ size: 4,
+ usage,
+ mappedAtCreation
+ });
+
+ if (mapMode !== undefined) {
+ if (mappedAtCreation) {
+ buf.unmap();
+ }
+ await buf.mapAsync(mapMode);
+ }
+ if (unmapBeforeDestroy) {
+ buf.unmap();
+ }
+
+ buf.destroy();
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/mapping.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/mapping.spec.js
new file mode 100644
index 0000000000..3d27f02567
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/mapping.spec.js
@@ -0,0 +1,1125 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for GPUBuffer.mapAsync, GPUBuffer.unmap and GPUBuffer.getMappedRange.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { attemptGarbageCollection } from '../../../../common/util/collect_garbage.js';
+import { assert, unreachable } from '../../../../common/util/util.js';
+import { kBufferUsages } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import { ValidationTest } from '../validation_test.js';
+
+class F extends ValidationTest {
+ async testMapAsyncCall(
+ expectation,
+
+
+ buffer,
+ mode,
+ offset,
+ size)
+ {
+ if (expectation === 'success') {
+ const p = buffer.mapAsync(mode, offset, size);
+ await p;
+ } else {
+ let p;
+ this.expectValidationError(() => {
+ p = buffer.mapAsync(mode, offset, size);
+ }, expectation.validationError);
+ let caught = false;
+ let rejectedEarly = false;
+ // If mapAsync rejected early, microtask A will run before B.
+ // If not, B will run before A.
+ p.catch(() => {
+ // Microtask A
+ caught = true;
+ });
+ queueMicrotask(() => {
+ // Microtask B
+ rejectedEarly = caught;
+ });
+ 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'
+ );
+ }
+ }
+ }
+
+ testGetMappedRangeCall(success, buffer, offset, size) {
+ if (success) {
+ const data = buffer.getMappedRange(offset, size);
+ this.expect(data instanceof ArrayBuffer);
+ if (size !== undefined) {
+ this.expect(data.byteLength === size);
+ }
+ } else {
+ this.shouldThrow('OperationError', () => {
+ buffer.getMappedRange(offset, size);
+ });
+ }
+ }
+
+ createMappableBuffer(type, size) {
+ switch (type) {
+ case GPUMapMode.READ:
+ return this.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.MAP_READ
+ });
+ case GPUMapMode.WRITE:
+ return this.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.MAP_WRITE
+ });
+ default:
+ unreachable();
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kMapModeOptions = [GPUConst.MapMode.READ, GPUConst.MapMode.WRITE];
+const kOffsetAlignment = 8;
+const kSizeAlignment = 4;
+
+g.test('mapAsync,usage').
+desc(
+ `Test the usage validation for mapAsync.
+
+ For each buffer usage:
+ For GPUMapMode.READ, GPUMapMode.WRITE, and 0:
+ Test that the mapAsync call is valid iff the mapping usage is not 0 and the buffer usage
+ the mapMode flag.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combineWithParams([
+{ mapMode: GPUConst.MapMode.READ, validUsage: GPUConst.BufferUsage.MAP_READ },
+{ mapMode: GPUConst.MapMode.WRITE, validUsage: GPUConst.BufferUsage.MAP_WRITE },
+// Using mapMode 0 is never valid, so there is no validUsage.
+{ mapMode: 0, validUsage: null }]
+).
+combine('usage', kBufferUsages)
+).
+fn(async (t) => {
+ const { mapMode, validUsage, usage } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage
+ });
+
+ const successParam =
+ usage === validUsage ?
+ 'success' :
+ {
+ validationError: true,
+ earlyRejection: false,
+ rejectName: 'OperationError'
+ };
+ await t.testMapAsyncCall(successParam, buffer, mapMode);
+});
+
+g.test('mapAsync,invalidBuffer').
+desc('Test that mapAsync is an error when called on an invalid buffer.').
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const buffer = t.getErrorBuffer();
+ await t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+});
+
+g.test('mapAsync,state,destroyed').
+desc('Test that mapAsync is an error when called on a destroyed buffer.').
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 16);
+
+ // Start mapping the buffer, we are going to destroy it before it resolves so it will reject
+ // the mapping promise with an AbortError.
+ const pending = t.testMapAsyncCall(
+ { validationError: false, earlyRejection: false, rejectName: 'AbortError' },
+ buffer,
+ mapMode
+ );
+
+ buffer.destroy();
+ await t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+
+ await pending;
+});
+
+g.test('mapAsync,state,mappedAtCreation').
+desc(
+ `Test that mapAsync is an error when called on a buffer mapped at creation,
+ but succeeds after unmapping it.`
+).
+paramsSubcasesOnly([
+{ mapMode: GPUConst.MapMode.READ, validUsage: GPUConst.BufferUsage.MAP_READ },
+{ mapMode: GPUConst.MapMode.WRITE, validUsage: GPUConst.BufferUsage.MAP_WRITE }]
+).
+fn(async (t) => {
+ const { mapMode, validUsage } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: validUsage,
+ mappedAtCreation: true
+ });
+ await t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+
+ buffer.unmap();
+ await t.testMapAsyncCall('success', buffer, mapMode);
+});
+
+g.test('mapAsync,state,mapped').
+desc(
+ `Test that mapAsync is an error when called on a mapped buffer, but succeeds
+ after unmapping it.`
+).
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+
+ const buffer = t.createMappableBuffer(mapMode, 16);
+ await t.testMapAsyncCall('success', buffer, mapMode);
+ await t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+
+ buffer.unmap();
+ await t.testMapAsyncCall('success', buffer, mapMode);
+});
+
+g.test('mapAsync,state,mappingPending').
+desc(
+ `Test that mapAsync is rejected when called on a buffer that is being mapped,
+ but succeeds after the previous mapping request is cancelled.`
+).
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+
+ const buffer = t.createMappableBuffer(mapMode, 16);
+
+ // Start mapping the buffer, we are going to unmap it before it resolves so it will reject
+ // the mapping promise with an AbortError.
+ const pending0 = t.testMapAsyncCall(
+ { validationError: false, earlyRejection: false, rejectName: 'AbortError' },
+ buffer,
+ mapMode
+ );
+
+ // Do the test of mapAsync while [[pending_map]] is non-null. It has to be synchronous so
+ // that we can unmap the previous mapping in the same stack frame and testing this one doesn't
+ // get canceled, but instead is rejected.
+ const pending1 = t.testMapAsyncCall(
+ { validationError: false, earlyRejection: true, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+
+ // Unmap the first mapping. It should now be possible to successfully call mapAsync
+ // This unmap should cause the first mapAsync rejection.
+ buffer.unmap();
+ await t.testMapAsyncCall('success', buffer, mapMode);
+
+ await pending0;
+ await pending1;
+});
+
+g.test('mapAsync,sizeUnspecifiedOOB').
+desc(
+ `Test that mapAsync with size unspecified rejects if offset > buffer.[[size]],
+ with various cases at the limits of the buffer size or with a misaligned offset.
+ Also test for an empty buffer.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('mapMode', kMapModeOptions).
+combineWithParams([
+// 0 size buffer.
+{ bufferSize: 0, offset: 0 },
+{ bufferSize: 0, offset: 1 },
+{ bufferSize: 0, offset: kOffsetAlignment },
+
+// Test with a buffer that's not empty.
+{ bufferSize: 16, offset: 0 },
+{ bufferSize: 16, offset: kOffsetAlignment },
+{ bufferSize: 16, offset: 16 },
+{ bufferSize: 16, offset: 17 },
+{ bufferSize: 16, offset: 16 + kOffsetAlignment }]
+)
+).
+fn(async (t) => {
+ const { mapMode, bufferSize, offset } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+
+ const successParam =
+ offset <= bufferSize ?
+ 'success' :
+ {
+ validationError: true,
+ earlyRejection: false,
+ rejectName: 'OperationError'
+ };
+ await t.testMapAsyncCall(successParam, buffer, mapMode, offset);
+});
+
+g.test('mapAsync,offsetAndSizeAlignment').
+desc("Test that mapAsync fails if the alignment of offset and size isn't correct.").
+paramsSubcasesOnly((u) =>
+u.
+combine('mapMode', kMapModeOptions).
+combine('offset', [0, kOffsetAlignment, kOffsetAlignment / 2]).
+combine('size', [0, kSizeAlignment, kSizeAlignment / 2])
+).
+fn(async (t) => {
+ const { mapMode, offset, size } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 16);
+
+ const successParam =
+ offset % kOffsetAlignment === 0 && size % kSizeAlignment === 0 ?
+ 'success' :
+ {
+ validationError: true,
+ earlyRejection: false,
+ rejectName: 'OperationError'
+ };
+ await t.testMapAsyncCall(successParam, buffer, mapMode, offset, size);
+});
+
+g.test('mapAsync,offsetAndSizeOOB').
+desc('Test that mapAsync fails if offset + size is larger than the buffer size.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('mapMode', kMapModeOptions).
+combineWithParams([
+// For a 0 size buffer
+{ bufferSize: 0, offset: 0, size: 0 },
+{ bufferSize: 0, offset: 0, size: 4 },
+{ bufferSize: 0, offset: 8, size: 0 },
+
+// For a small buffer
+{ bufferSize: 16, offset: 0, size: 16 },
+{ bufferSize: 16, offset: kOffsetAlignment, size: 16 },
+
+{ bufferSize: 16, offset: 16, size: 0 },
+{ bufferSize: 16, offset: 16, size: kSizeAlignment },
+
+{ bufferSize: 16, offset: 8, size: 0 },
+{ bufferSize: 16, offset: 8, size: 8 },
+{ bufferSize: 16, offset: 8, size: 8 + kSizeAlignment },
+
+// For a larger buffer
+{ bufferSize: 1024, offset: 0, size: 1024 },
+{ bufferSize: 1024, offset: kOffsetAlignment, size: 1024 },
+
+{ bufferSize: 1024, offset: 1024, size: 0 },
+{ bufferSize: 1024, offset: 1024, size: kSizeAlignment },
+
+{ bufferSize: 1024, offset: 512, size: 0 },
+{ bufferSize: 1024, offset: 512, size: 512 },
+{ bufferSize: 1024, offset: 512, size: 512 + kSizeAlignment }]
+)
+).
+fn(async (t) => {
+ const { mapMode, bufferSize, size, offset } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+
+ const successParam =
+ offset + size <= bufferSize ?
+ 'success' :
+ {
+ validationError: true,
+ earlyRejection: false,
+ rejectName: 'OperationError'
+ };
+ await t.testMapAsyncCall(successParam, buffer, mapMode, offset, size);
+});
+
+g.test('mapAsync,earlyRejection').
+desc("Test that mapAsync fails immediately if it's pending map.").
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions).combine('offset2', [0, 8])).
+fn(async (t) => {
+ const { mapMode, offset2 } = t.params;
+
+ const bufferSize = 16;
+ const mapSize = 8;
+ const offset1 = 0;
+
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+ const p1 = buffer.mapAsync(mapMode, offset1, mapSize); // succeeds
+ await t.testMapAsyncCall(
+ {
+ validationError: false,
+ earlyRejection: true,
+ rejectName: 'OperationError'
+ },
+ buffer,
+ mapMode,
+ offset2,
+ mapSize
+ );
+ await p1; // ensure the original map still succeeds
+});
+
+g.test('mapAsync,abort_over_invalid_error').
+desc(
+ `Test that unmap abort error should have precedence over validation error
+TODO
+ - Add other validation error test (eg. offset is not a multiple of 8)
+ `
+).
+paramsSubcasesOnly((u) =>
+u.combine('mapMode', kMapModeOptions).combine('unmapBeforeResolve', [true, false])
+).
+fn(async (t) => {
+ const { mapMode, unmapBeforeResolve } = t.params;
+ const bufferSize = 8;
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+ await buffer.mapAsync(mapMode);
+
+ if (unmapBeforeResolve) {
+ // unmap abort error should have precedence over validation error
+ const pending = t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'AbortError' },
+ buffer,
+ mapMode
+ );
+ buffer.unmap();
+ await pending;
+ } else {
+ // map on already mapped buffer should cause validation error
+ await t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+ buffer.unmap();
+ }
+});
+
+g.test('getMappedRange,state,mapped').
+desc('Test that it is valid to call getMappedRange in the mapped state').
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const bufferSize = 16;
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+ await buffer.mapAsync(mapMode);
+
+ const data = buffer.getMappedRange();
+ t.expect(data instanceof ArrayBuffer);
+ t.expect(data.byteLength === bufferSize);
+
+ // map on already mapped buffer should be rejected
+ const pending = t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+ t.expect(data.byteLength === bufferSize);
+ await pending;
+
+ buffer.unmap();
+
+ t.expect(data.byteLength === 0);
+});
+
+g.test('getMappedRange,state,mappedAtCreation').
+desc(
+ `Test that, in the mapped-at-creation state, it is valid to call getMappedRange, for all buffer usages,
+ and invalid to call mapAsync, for all map modes.`
+).
+paramsSubcasesOnly((u) =>
+u.combine('bufferUsage', kBufferUsages).combine('mapMode', kMapModeOptions)
+).
+fn(async (t) => {
+ const { bufferUsage, mapMode } = t.params;
+ const bufferSize = 16;
+ const buffer = t.device.createBuffer({
+ usage: bufferUsage,
+ size: bufferSize,
+ mappedAtCreation: true
+ });
+
+ const data = buffer.getMappedRange();
+ t.expect(data instanceof ArrayBuffer);
+ t.expect(data.byteLength === bufferSize);
+
+ // map on already mapped buffer should be rejected
+ const pending = t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+ t.expect(data.byteLength === bufferSize);
+ await pending;
+
+ buffer.unmap();
+
+ t.expect(data.byteLength === 0);
+});
+
+g.test('getMappedRange,state,invalid_mappedAtCreation').
+desc(
+ `mappedAtCreation should return a mapped buffer, even if the buffer is invalid.
+Like VRAM allocation (see map_oom), validation can be performed asynchronously (in the GPU process)
+so the Content process doesn't necessarily know the buffer is invalid.`
+).
+fn((t) => {
+ const buffer = t.expectGPUError('validation', () =>
+ t.device.createBuffer({
+ mappedAtCreation: true,
+ size: 16,
+ usage: 0xffff_ffff // Invalid usage
+ })
+ );
+
+ // Should still be valid.
+ buffer.getMappedRange();
+});
+
+g.test('getMappedRange,state,mappedAgain').
+desc(
+ 'Test that it is valid to call getMappedRange in the mapped state, even if there is a duplicate mapAsync before'
+).
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 16);
+ await buffer.mapAsync(mapMode);
+
+ // call mapAsync again on already mapped buffer should fail
+ await t.testMapAsyncCall(
+ { validationError: true, earlyRejection: false, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+
+ // getMapppedRange should still success
+ t.testGetMappedRangeCall(true, buffer);
+});
+
+g.test('getMappedRange,state,unmapped').
+desc(
+ `Test that it is invalid to call getMappedRange in the unmapped state.
+Test for various cases of being unmapped: at creation, after a mapAsync call or after being created mapped.`
+).
+fn(async (t) => {
+ // It is invalid to call getMappedRange when the buffer starts unmapped when created.
+ {
+ const buffer = t.createMappableBuffer(GPUMapMode.READ, 16);
+ t.testGetMappedRangeCall(false, buffer);
+ }
+
+ // It is invalid to call getMappedRange when the buffer is unmapped after mapAsync.
+ {
+ const buffer = t.createMappableBuffer(GPUMapMode.READ, 16);
+ await buffer.mapAsync(GPUMapMode.READ);
+ buffer.unmap();
+ t.testGetMappedRangeCall(false, buffer);
+ }
+
+ // It is invalid to call getMappedRange when the buffer is unmapped after mappedAtCreation.
+ {
+ const buffer = t.device.createBuffer({
+ usage: GPUBufferUsage.MAP_READ,
+ size: 16,
+ mappedAtCreation: true
+ });
+ buffer.unmap();
+ t.testGetMappedRangeCall(false, buffer);
+ }
+});
+
+g.test('getMappedRange,subrange,mapped').
+desc(
+ `Test that old getMappedRange returned arraybuffer does not exist after unmap, and newly returned
+ arraybuffer after new map has correct subrange`
+).
+params((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const bufferSize = 16;
+ const offset = 8;
+ const subrangeSize = bufferSize - offset;
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+ await buffer.mapAsync(mapMode);
+
+ const data0 = buffer.getMappedRange();
+ t.expect(data0 instanceof ArrayBuffer);
+ t.expect(data0.byteLength === bufferSize);
+
+ buffer.unmap();
+ t.expect(data0.byteLength === 0);
+
+ await buffer.mapAsync(mapMode, offset);
+ const data1 = buffer.getMappedRange(8);
+
+ t.expect(data0.byteLength === 0);
+ t.expect(data1.byteLength === subrangeSize);
+});
+
+g.test('getMappedRange,subrange,mappedAtCreation').
+desc(
+ `Test that old getMappedRange returned arraybuffer does not exist after unmap and newly returned
+ arraybuffer after new map has correct subrange`
+).
+fn(async (t) => {
+ const bufferSize = 16;
+ const offset = 8;
+ const subrangeSize = bufferSize - offset;
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
+ mappedAtCreation: true
+ });
+
+ const data0 = buffer.getMappedRange();
+ t.expect(data0 instanceof ArrayBuffer);
+ t.expect(data0.byteLength === bufferSize);
+
+ buffer.unmap();
+ t.expect(data0.byteLength === 0);
+
+ await buffer.mapAsync(GPUMapMode.READ, offset);
+ const data1 = buffer.getMappedRange(8);
+
+ t.expect(data0.byteLength === 0);
+ t.expect(data1.byteLength === subrangeSize);
+});
+
+g.test('getMappedRange,state,destroyed').
+desc(
+ `Test that it is invalid to call getMappedRange in the destroyed state.
+Test for various cases of being destroyed: at creation, after a mapAsync call or after being created mapped.`
+).
+fn(async (t) => {
+ // It is invalid to call getMappedRange when the buffer is destroyed when unmapped.
+ {
+ const buffer = t.createMappableBuffer(GPUMapMode.READ, 16);
+ buffer.destroy();
+ t.testGetMappedRangeCall(false, buffer);
+ }
+
+ // It is invalid to call getMappedRange when the buffer is destroyed when mapped.
+ {
+ const buffer = t.createMappableBuffer(GPUMapMode.READ, 16);
+ await buffer.mapAsync(GPUMapMode.READ);
+ buffer.destroy();
+ t.testGetMappedRangeCall(false, buffer);
+ }
+
+ // It is invalid to call getMappedRange when the buffer is destroyed when mapped at creation.
+ {
+ const buffer = t.device.createBuffer({
+ usage: GPUBufferUsage.MAP_READ,
+ size: 16,
+ mappedAtCreation: true
+ });
+ buffer.destroy();
+ t.testGetMappedRangeCall(false, buffer);
+ }
+});
+
+g.test('getMappedRange,state,mappingPending').
+desc(`Test that it is invalid to call getMappedRange in the mappingPending state.`).
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 16);
+
+ /* noawait */const mapping0 = buffer.mapAsync(mapMode);
+ // seconding mapping should be rejected
+ const mapping1 = t.testMapAsyncCall(
+ { validationError: false, earlyRejection: true, rejectName: 'OperationError' },
+ buffer,
+ mapMode
+ );
+
+ // invalid in mappingPending state
+ t.testGetMappedRangeCall(false, buffer);
+
+ await mapping0;
+
+ // valid after buffer is mapped
+ t.testGetMappedRangeCall(true, buffer);
+
+ await mapping1;
+});
+
+g.test('getMappedRange,offsetAndSizeAlignment,mapped').
+desc(`Test that getMappedRange fails if the alignment of offset and size isn't correct.`).
+params((u) =>
+u.
+combine('mapMode', kMapModeOptions).
+beginSubcases().
+combine('mapOffset', [0, kOffsetAlignment]).
+combine('offset', [0, kOffsetAlignment, kOffsetAlignment / 2]).
+combine('size', [0, kSizeAlignment, kSizeAlignment / 2])
+).
+fn(async (t) => {
+ const { mapMode, mapOffset, offset, size } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 32);
+ await buffer.mapAsync(mapMode, mapOffset);
+
+ const success = offset % kOffsetAlignment === 0 && size % kSizeAlignment === 0;
+ t.testGetMappedRangeCall(success, buffer, offset + mapOffset, size);
+});
+
+g.test('getMappedRange,offsetAndSizeAlignment,mappedAtCreation').
+desc(`Test that getMappedRange fails if the alignment of offset and size isn't correct.`).
+paramsSubcasesOnly((u) =>
+u.
+combine('offset', [0, kOffsetAlignment, kOffsetAlignment / 2]).
+combine('size', [0, kSizeAlignment, kSizeAlignment / 2])
+).
+fn((t) => {
+ const { offset, size } = t.params;
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST,
+ mappedAtCreation: true
+ });
+ const success = offset % kOffsetAlignment === 0 && size % kSizeAlignment === 0;
+ t.testGetMappedRangeCall(success, buffer, offset, size);
+});
+
+g.test('getMappedRange,sizeAndOffsetOOB,mappedAtCreation').
+desc(
+ `Test that getMappedRange size + offset must be less than the buffer size for a
+ buffer mapped at creation. (and offset has not constraints on its own)`
+).
+paramsSubcasesOnly([
+// Tests for a zero-sized buffer, with and without a size defined.
+{ bufferSize: 0, offset: undefined, size: undefined },
+{ bufferSize: 0, offset: undefined, size: 0 },
+{ bufferSize: 0, offset: undefined, size: kSizeAlignment },
+{ bufferSize: 0, offset: 0, size: undefined },
+{ bufferSize: 0, offset: 0, size: 0 },
+{ bufferSize: 0, offset: kOffsetAlignment, size: undefined },
+{ bufferSize: 0, offset: kOffsetAlignment, size: 0 },
+
+// Tests for a non-empty buffer, with an undefined offset.
+{ bufferSize: 80, offset: undefined, size: 80 },
+{ bufferSize: 80, offset: undefined, size: 80 + kSizeAlignment },
+
+// Tests for a non-empty buffer, with an undefined size.
+{ bufferSize: 80, offset: undefined, size: undefined },
+{ bufferSize: 80, offset: 0, size: undefined },
+{ bufferSize: 80, offset: kOffsetAlignment, size: undefined },
+{ bufferSize: 80, offset: 80, size: undefined },
+{ bufferSize: 80, offset: 80 + kOffsetAlignment, size: undefined },
+
+// Tests for a non-empty buffer with a size defined.
+{ bufferSize: 80, offset: 0, size: 80 },
+{ bufferSize: 80, offset: 0, size: 80 + kSizeAlignment },
+{ bufferSize: 80, offset: kOffsetAlignment, size: 80 },
+
+{ bufferSize: 80, offset: 40, size: 40 },
+{ bufferSize: 80, offset: 40 + kOffsetAlignment, size: 40 },
+{ bufferSize: 80, offset: 40, size: 40 + kSizeAlignment }]
+).
+fn((t) => {
+ const { bufferSize, offset, size } = t.params;
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_DST,
+ mappedAtCreation: true
+ });
+
+ const actualOffset = offset ?? 0;
+ const actualSize = size ?? bufferSize - actualOffset;
+
+ const success = actualOffset <= bufferSize && actualOffset + actualSize <= bufferSize;
+ t.testGetMappedRangeCall(success, buffer, offset, size);
+});
+
+g.test('getMappedRange,sizeAndOffsetOOB,mapped').
+desc('Test that getMappedRange size + offset must be less than the mapAsync range.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('mapMode', kMapModeOptions).
+combineWithParams([
+// Tests for an empty buffer, and implicit mapAsync size.
+{ bufferSize: 0, mapOffset: 0, mapSize: undefined, offset: undefined, size: undefined },
+{ bufferSize: 0, mapOffset: 0, mapSize: undefined, offset: undefined, size: 0 },
+{
+ bufferSize: 0,
+ mapOffset: 0,
+ mapSize: undefined,
+ offset: undefined,
+ size: kSizeAlignment
+},
+{ bufferSize: 0, mapOffset: 0, mapSize: undefined, offset: 0, size: undefined },
+{ bufferSize: 0, mapOffset: 0, mapSize: undefined, offset: 0, size: 0 },
+{
+ bufferSize: 0,
+ mapOffset: 0,
+ mapSize: undefined,
+ offset: kOffsetAlignment,
+ size: undefined
+},
+{ bufferSize: 0, mapOffset: 0, mapSize: undefined, offset: kOffsetAlignment, size: 0 },
+
+// Tests for an empty buffer, and explicit mapAsync size.
+{ bufferSize: 0, mapOffset: 0, mapSize: 0, offset: undefined, size: undefined },
+{ bufferSize: 0, mapOffset: 0, mapSize: 0, offset: 0, size: undefined },
+{ bufferSize: 0, mapOffset: 0, mapSize: 0, offset: 0, size: 0 },
+{ bufferSize: 0, mapOffset: 0, mapSize: 0, offset: kOffsetAlignment, size: undefined },
+{ bufferSize: 0, mapOffset: 0, mapSize: 0, offset: kOffsetAlignment, size: 0 },
+
+// Test for a fully implicit mapAsync call
+{ bufferSize: 80, mapOffset: undefined, mapSize: undefined, offset: 0, size: 80 },
+{
+ bufferSize: 80,
+ mapOffset: undefined,
+ mapSize: undefined,
+ offset: 0,
+ size: 80 + kSizeAlignment
+},
+{
+ bufferSize: 80,
+ mapOffset: undefined,
+ mapSize: undefined,
+ offset: kOffsetAlignment,
+ size: 80
+},
+
+// Test for a mapAsync call with an implicit size
+{ bufferSize: 80, mapOffset: 24, mapSize: undefined, offset: 24, size: 80 - 24 },
+{
+ bufferSize: 80,
+ mapOffset: 24,
+ mapSize: undefined,
+ offset: 0,
+ size: 80 - 24 + kSizeAlignment
+},
+{
+ bufferSize: 80,
+ mapOffset: 24,
+ mapSize: undefined,
+ offset: kOffsetAlignment,
+ size: 80 - 24
+},
+
+// Test for a non-empty buffer fully mapped.
+{ bufferSize: 80, mapOffset: 0, mapSize: 80, offset: 0, size: 80 },
+{ bufferSize: 80, mapOffset: 0, mapSize: 80, offset: kOffsetAlignment, size: 80 },
+{ bufferSize: 80, mapOffset: 0, mapSize: 80, offset: 0, size: 80 + kSizeAlignment },
+
+{ bufferSize: 80, mapOffset: 0, mapSize: 80, offset: 40, size: 40 },
+{ bufferSize: 80, mapOffset: 0, mapSize: 80, offset: 40 + kOffsetAlignment, size: 40 },
+{ bufferSize: 80, mapOffset: 0, mapSize: 80, offset: 40, size: 40 + kSizeAlignment },
+
+// Test for a buffer partially mapped.
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: 24, size: 40 },
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: 24 - kOffsetAlignment, size: 40 },
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: 24 + kOffsetAlignment, size: 40 },
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: 24, size: 40 + kSizeAlignment },
+
+// Test for a partially mapped buffer with implicit size and offset for getMappedRange.
+// - Buffer partially mapped in the middle
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: undefined, size: undefined },
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: 0, size: undefined },
+{ bufferSize: 80, mapOffset: 24, mapSize: 40, offset: 24, size: undefined },
+// - Buffer partially mapped to the end
+{ bufferSize: 80, mapOffset: 24, mapSize: undefined, offset: 24, size: undefined },
+{ bufferSize: 80, mapOffset: 24, mapSize: undefined, offset: 80, size: undefined },
+// - Buffer partially mapped from the start
+{ bufferSize: 80, mapOffset: 0, mapSize: 64, offset: undefined, size: undefined },
+{ bufferSize: 80, mapOffset: 0, mapSize: 64, offset: undefined, size: 64 }]
+)
+).
+fn(async (t) => {
+ const { mapMode, bufferSize, mapOffset, mapSize, offset, size } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, bufferSize);
+ await buffer.mapAsync(mapMode, mapOffset, mapSize);
+
+ const actualMapOffset = mapOffset ?? 0;
+ const actualMapSize = mapSize ?? bufferSize - actualMapOffset;
+
+ const actualOffset = offset ?? 0;
+ const actualSize = size ?? bufferSize - actualOffset;
+
+ const success =
+ actualOffset >= actualMapOffset &&
+ actualOffset <= bufferSize &&
+ actualOffset + actualSize <= actualMapOffset + actualMapSize;
+ t.testGetMappedRangeCall(success, buffer, offset, size);
+});
+
+g.test('getMappedRange,disjointRanges').
+desc('Test that the ranges asked through getMappedRange must be disjoint.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('remapBetweenCalls', [false, true]).
+combineWithParams([
+// Disjoint ranges with one that's empty.
+{ offset1: 8, size1: 0, offset2: 8, size2: 8 },
+{ offset1: 16, size1: 0, offset2: 8, size2: 8 },
+
+{ offset1: 8, size1: 8, offset2: 8, size2: 0 },
+{ offset1: 8, size1: 8, offset2: 16, size2: 0 },
+
+// Disjoint ranges with both non-empty.
+{ offset1: 0, size1: 8, offset2: 8, size2: 8 },
+{ offset1: 16, size1: 8, offset2: 8, size2: 8 },
+
+{ offset1: 8, size1: 8, offset2: 0, size2: 8 },
+{ offset1: 8, size1: 8, offset2: 16, size2: 8 },
+
+// Empty range contained inside another one.
+{ offset1: 16, size1: 20, offset2: 24, size2: 0 },
+{ offset1: 24, size1: 0, offset2: 16, size2: 20 },
+
+// Ranges that overlap only partially.
+{ offset1: 16, size1: 20, offset2: 8, size2: 20 },
+{ offset1: 16, size1: 20, offset2: 32, size2: 20 },
+
+// Ranges that include one another.
+{ offset1: 0, size1: 80, offset2: 16, size2: 20 },
+{ offset1: 16, size1: 20, offset2: 0, size2: 80 }]
+)
+).
+fn(async (t) => {
+ const { offset1, size1, offset2, size2, remapBetweenCalls } = t.params;
+ const buffer = t.device.createBuffer({ size: 80, usage: GPUBufferUsage.MAP_READ });
+ await buffer.mapAsync(GPUMapMode.READ);
+
+ t.testGetMappedRangeCall(true, buffer, offset1, size1);
+
+ if (remapBetweenCalls) {
+ buffer.unmap();
+ await buffer.mapAsync(GPUMapMode.READ);
+ }
+
+ const range1StartsAfter2 = offset1 >= offset2 + size2;
+ const range2StartsAfter1 = offset2 >= offset1 + size1;
+ const disjoint = range1StartsAfter2 || range2StartsAfter1;
+ const success = disjoint || remapBetweenCalls;
+
+ t.testGetMappedRangeCall(success, buffer, offset2, size2);
+});
+
+g.test('getMappedRange,disjoinRanges_many').
+desc('Test getting a lot of small ranges, and that the disjoint check checks them all.').
+fn(async (t) => {
+ const kStride = 256;
+ const kNumStrides = 256;
+
+ const buffer = t.device.createBuffer({
+ size: kStride * kNumStrides,
+ usage: GPUBufferUsage.MAP_READ
+ });
+ await buffer.mapAsync(GPUMapMode.READ);
+
+ // Get a lot of small mapped ranges.
+ for (let stride = 0; stride < kNumStrides; stride++) {
+ t.testGetMappedRangeCall(true, buffer, stride * kStride, 8);
+ }
+
+ // Check for each range it is invalid to get a range that overlaps it and check that it is valid
+ // to get ranges for the rest of the buffer.
+ for (let stride = 0; stride < kNumStrides; stride++) {
+ t.testGetMappedRangeCall(false, buffer, stride * kStride, kStride);
+ t.testGetMappedRangeCall(true, buffer, stride * kStride + 8, kStride - 8);
+ }
+});
+
+g.test('unmap,state,unmapped').
+desc(
+ `Test it is valid to call unmap on a buffer that is unmapped (at creation, or after
+ mappedAtCreation or mapAsync)`
+).
+fn(async (t) => {
+ // It is valid to call unmap after creation of an unmapped buffer.
+ {
+ const buffer = t.device.createBuffer({ size: 16, usage: GPUBufferUsage.MAP_READ });
+ buffer.unmap();
+ }
+
+ // It is valid to call unmap after unmapping a mapAsynced buffer.
+ {
+ const buffer = t.createMappableBuffer(GPUMapMode.READ, 16);
+ await buffer.mapAsync(GPUMapMode.READ);
+ buffer.unmap();
+ buffer.unmap();
+ }
+
+ // It is valid to call unmap after unmapping a mappedAtCreation buffer.
+ {
+ const buffer = t.device.createBuffer({
+ usage: GPUBufferUsage.MAP_READ,
+ size: 16,
+ mappedAtCreation: true
+ });
+ buffer.unmap();
+ buffer.unmap();
+ }
+});
+
+g.test('unmap,state,destroyed').
+desc(
+ `Test it is valid to call unmap on a buffer that is destroyed (at creation, or after
+ mappedAtCreation or mapAsync)`
+).
+fn(async (t) => {
+ // It is valid to call unmap after destruction of an unmapped buffer.
+ {
+ const buffer = t.device.createBuffer({ size: 16, usage: GPUBufferUsage.MAP_READ });
+ buffer.destroy();
+ buffer.unmap();
+ }
+
+ // It is valid to call unmap after destroying a mapAsynced buffer.
+ {
+ const buffer = t.createMappableBuffer(GPUMapMode.READ, 16);
+ await buffer.mapAsync(GPUMapMode.READ);
+ buffer.destroy();
+ buffer.unmap();
+ }
+
+ // It is valid to call unmap after destroying a mappedAtCreation buffer.
+ {
+ const buffer = t.device.createBuffer({
+ usage: GPUBufferUsage.MAP_READ,
+ size: 16,
+ mappedAtCreation: true
+ });
+ buffer.destroy();
+ buffer.unmap();
+ }
+});
+
+g.test('unmap,state,mappedAtCreation').
+desc('Test it is valid to call unmap on a buffer mapped at creation, for various usages').
+paramsSubcasesOnly((u) =>
+u //
+.combine('bufferUsage', kBufferUsages)
+).
+fn((t) => {
+ const { bufferUsage } = t.params;
+ const buffer = t.device.createBuffer({ size: 16, usage: bufferUsage, mappedAtCreation: true });
+
+ buffer.unmap();
+});
+
+g.test('unmap,state,mapped').
+desc("Test it is valid to call unmap on a buffer that's mapped").
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 16);
+
+ await buffer.mapAsync(mapMode);
+ buffer.unmap();
+});
+
+g.test('unmap,state,mappingPending').
+desc("Test it is valid to call unmap on a buffer that's being mapped").
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+ const buffer = t.createMappableBuffer(mapMode, 16);
+
+ const pending = t.testMapAsyncCall(
+ { validationError: false, earlyRejection: false, rejectName: 'AbortError' },
+ buffer,
+ mapMode
+ );
+ buffer.unmap();
+ await pending;
+});
+
+g.test('gc_behavior,mappedAtCreation').
+desc(
+ "Test that GCing the buffer while mappings are handed out doesn't invalidate them - mappedAtCreation case"
+).
+fn(async (t) => {
+ let buffer = null;
+ buffer = t.device.createBuffer({
+ size: 256,
+ usage: GPUBufferUsage.COPY_DST,
+ mappedAtCreation: true
+ });
+
+ // Write some non-zero data to the buffer.
+ const contents = new Uint32Array(buffer.getMappedRange());
+ for (let i = 0; i < contents.length; i++) {
+ contents[i] = i;
+ }
+
+ // Trigger garbage collection that should collect the buffer (or as if it collected it)
+ // NOTE: This won't fail unless the browser immediately starts reusing the memory, or gives it
+ // back to the OS. One good option for browsers to check their logic is good is to zero-out the
+ // memory on GPUBuffer (or internal gpu::Buffer-like object) destruction.
+ buffer = null;
+ await attemptGarbageCollection();
+
+ // Use the mapping again both for read and write, it should work.
+ for (let i = 0; i < contents.length; i++) {
+ t.expect(contents[i] === i);
+ contents[i] = i + 1;
+ }
+});
+
+g.test('gc_behavior,mapAsync').
+desc(
+ "Test that GCing the buffer while mappings are handed out doesn't invalidate them - mapAsync case"
+).
+paramsSubcasesOnly((u) => u.combine('mapMode', kMapModeOptions)).
+fn(async (t) => {
+ const { mapMode } = t.params;
+
+ let buffer = null;
+ buffer = t.createMappableBuffer(mapMode, 256);
+ await buffer.mapAsync(mapMode);
+
+ // Write some non-zero data to the buffer.
+ const contents = new Uint32Array(buffer.getMappedRange());
+ for (let i = 0; i < contents.length; i++) {
+ contents[i] = i;
+ }
+
+ // Trigger garbage collection that should collect the buffer (or as if it collected it)
+ // NOTE: This won't fail unless the browser immediately starts reusing the memory, or gives it
+ // back to the OS. One good option for browsers to check their logic is good is to zero-out the
+ // memory on GPUBuffer (or internal gpu::Buffer-like object) destruction.
+ buffer = null;
+ await attemptGarbageCollection();
+
+ // Use the mapping again both for read and write, it should work.
+ for (let i = 0; i < contents.length; i++) {
+ t.expect(contents[i] === i);
+ contents[i] = i + 1;
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/threading.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/threading.spec.js
new file mode 100644
index 0000000000..3b22b14879
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/buffer/threading.spec.js
@@ -0,0 +1,14 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+TODO:
+- Try to map on one thread while {pending, mapped, mappedAtCreation, mappedAtCreation+unmap+mapped}
+ on another thread.
+- Invalid to postMessage a mapped range's ArrayBuffer or ArrayBufferView
+ {with, without} it being in the transfer array.
+- Copy GPUBuffer to another thread while {pending, mapped mappedAtCreation} on {same,diff} thread
+ (valid), then try to map on that thread (invalid)
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/query_types.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/query_types.spec.js
new file mode 100644
index 0000000000..02bfb09b64
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/query_types.spec.js
@@ -0,0 +1,76 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for capability checking for features enabling optional query types.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('createQuerySet').
+desc(
+ `
+ Tests that creating a query set throws a type error exception if the features don't contain
+ 'timestamp-query'.
+ - createQuerySet
+ - type {occlusion, timestamp}
+ - x= timestamp query {enable, disable}
+ `
+).
+params((u) =>
+u.
+combine('type', ['occlusion', 'timestamp']).
+combine('featureContainsTimestampQuery', [false, true])
+).
+beforeAllSubcases((t) => {
+ const { featureContainsTimestampQuery } = t.params;
+
+ const requiredFeatures = [];
+ if (featureContainsTimestampQuery) {
+ requiredFeatures.push('timestamp-query');
+ }
+
+ t.selectDeviceOrSkipTestCase({ requiredFeatures });
+}).
+fn((t) => {
+ const { type, featureContainsTimestampQuery } = t.params;
+
+ const count = 1;
+ const shouldException = type === 'timestamp' && !featureContainsTimestampQuery;
+
+ t.shouldThrow(shouldException ? 'TypeError' : false, () => {
+ t.device.createQuerySet({ type, count });
+ });
+});
+
+g.test('writeTimestamp').
+desc(
+ `
+ Tests that writing a timestamp throws a type error exception if the features don't contain
+ 'timestamp-query'.
+ `
+).
+params((u) => u.combine('featureContainsTimestampQuery', [false, true])).
+beforeAllSubcases((t) => {
+ const { featureContainsTimestampQuery } = t.params;
+
+ const requiredFeatures = [];
+ if (featureContainsTimestampQuery) {
+ requiredFeatures.push('timestamp-query');
+ }
+
+ t.selectDeviceOrSkipTestCase({ requiredFeatures });
+}).
+fn((t) => {
+ const { featureContainsTimestampQuery } = t.params;
+
+ const querySet = t.device.createQuerySet({
+ type: featureContainsTimestampQuery ? 'timestamp' : 'occlusion',
+ count: 1
+ });
+ const encoder = t.createEncoder('non-pass');
+
+ t.shouldThrow(featureContainsTimestampQuery ? false : 'TypeError', () => {
+ encoder.encoder.writeTimestamp(querySet, 0);
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/texture_formats.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/texture_formats.spec.js
new file mode 100644
index 0000000000..4e88bfc4ff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/features/texture_formats.spec.js
@@ -0,0 +1,463 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for capability checking for features enabling optional texture formats.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { getGPU } from '../../../../../common/util/navigator_gpu.js';
+import { assert } from '../../../../../common/util/util.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../../../format_info.js';
+import { kAllCanvasTypes, createCanvas } from '../../../../util/create_elements.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+const kOptionalTextureFormats = kAllTextureFormats.filter(
+ (t) => kTextureFormatInfo[t].feature !== undefined
+);
+
+g.test('texture_descriptor').
+desc(
+ `
+ Test creating a texture with an optional texture format will fail if the required optional feature
+ is not enabled.
+ `
+).
+params((u) =>
+u.combine('format', kOptionalTextureFormats).combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
+ t.device.createTexture({
+ format,
+ size: [formatInfo.blockWidth, formatInfo.blockHeight, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+ });
+});
+
+g.test('texture_descriptor_view_formats').
+desc(
+ `
+ Test creating a texture with view formats that have an optional texture format will fail if the
+ required optional feature is not enabled.
+ `
+).
+params((u) =>
+u.combine('format', kOptionalTextureFormats).combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
+ t.device.createTexture({
+ format,
+ size: [formatInfo.blockWidth, formatInfo.blockHeight, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ viewFormats: [format]
+ });
+ });
+});
+
+g.test('texture_view_descriptor').
+desc(
+ `
+ Test creating a texture view with all texture formats will fail if the required optional feature
+ is not enabled.
+ `
+).
+params((u) =>
+u.combine('format', kOptionalTextureFormats).combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ // If the required feature isn't enabled then the texture will fail to create and we won't be
+ // able to test createView, so pick and alternate guaranteed format instead. This will almost
+ // certainly not be view-compatible with the format being tested, but that doesn't matter since
+ // createView should throw an exception due to the format feature not being enabled before it
+ // has a chance to validate that the view and texture formats aren't compatible.
+ const textureFormat = enable_required_feature ? format : 'rgba8unorm';
+
+ const formatInfo = kTextureFormatInfo[format];
+ const testTexture = t.device.createTexture({
+ format: textureFormat,
+ size: [formatInfo.blockWidth, formatInfo.blockHeight, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+ const testViewDesc = {
+ format,
+ dimension: '2d',
+ aspect: 'all',
+ arrayLayerCount: 1,
+ baseMipLevel: 0,
+ mipLevelCount: 1,
+ baseArrayLayer: 0
+ };
+ t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
+ testTexture.createView(testViewDesc);
+ });
+});
+
+g.test('canvas_configuration').
+desc(
+ `
+ Test configuring a canvas with optional texture formats will throw an exception if the required
+ optional feature is not enabled. Otherwise, a validation error should be generated instead of
+ throwing an exception.
+ `
+).
+params((u) =>
+u.
+combine('format', kOptionalTextureFormats).
+combine('canvasType', kAllCanvasTypes).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, canvasType, enable_required_feature } = t.params;
+
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ const canvasConf = {
+ device: t.device,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ };
+
+ if (enable_required_feature) {
+ t.expectValidationError(() => {
+ ctx.configure(canvasConf);
+ });
+ } else {
+ t.shouldThrow('TypeError', () => {
+ ctx.configure(canvasConf);
+ });
+ }
+});
+
+g.test('canvas_configuration_view_formats').
+desc(
+ `
+ Test that configuring a canvas with view formats throws an exception if the required optional
+ feature is not enabled. Otherwise, a validation error should be generated instead of throwing an
+ exception.
+ `
+).
+params((u) =>
+u.
+combine('viewFormats', [
+...kOptionalTextureFormats.map((format) => [format]),
+['bgra8unorm', 'bc1-rgba-unorm'],
+['bc1-rgba-unorm', 'bgra8unorm']]
+).
+combine('canvasType', kAllCanvasTypes).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { viewFormats, enable_required_feature } = t.params;
+
+ if (enable_required_feature) {
+ t.selectDeviceForTextureFormatOrSkipTestCase(viewFormats);
+ }
+}).
+fn((t) => {
+ const { viewFormats, canvasType, enable_required_feature } = t.params;
+
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ const canvasConf = {
+ device: t.device,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ viewFormats: viewFormats
+ };
+
+ if (enable_required_feature) {
+ t.expectValidationError(() => {
+ ctx.configure(canvasConf);
+ });
+ } else {
+ t.shouldThrow('TypeError', () => {
+ ctx.configure(canvasConf);
+ });
+ }
+});
+
+g.test('storage_texture_binding_layout').
+desc(
+ `
+ Test creating a GPUStorageTextureBindingLayout with an optional texture format will fail if the
+ required optional feature are not enabled.
+
+ Note: This test has no cases if there are no optional texture formats supporting storage.
+ `
+).
+params((u) =>
+u.
+combine('format', kOptionalTextureFormats).
+filter((t) => !!kTextureFormatInfo[t.format].color?.storage).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: {
+ format
+ }
+ }]
+
+ });
+ });
+});
+
+g.test('color_target_state').
+desc(
+ `
+ Test creating a render pipeline with an optional texture format set in GPUColorTargetState will
+ fail if the required optional feature is not enabled.
+
+ Note: This test has no cases if there are no optional texture formats supporting color rendering.
+ `
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kOptionalTextureFormats).
+filter((t) => !!kTextureFormatInfo[t.format].colorRender).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { isAsync, format, enable_required_feature } = t.params;
+
+ t.doCreateRenderPipelineTest(
+ isAsync,
+ enable_required_feature,
+ {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main()-> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ }
+ },
+ 'TypeError'
+ );
+});
+
+g.test('depth_stencil_state').
+desc(
+ `
+ Test creating a render pipeline with an optional texture format set in GPUColorTargetState will
+ fail if the required optional feature is not enabled.
+ `
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kOptionalTextureFormats).
+filter((t) => !!(kTextureFormatInfo[t.format].depth || kTextureFormatInfo[t.format].stencil)).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { isAsync, format, enable_required_feature } = t.params;
+
+ t.doCreateRenderPipelineTest(
+ isAsync,
+ enable_required_feature,
+ {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main()-> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ depthStencil: {
+ format,
+ depthCompare: 'always',
+ depthWriteEnabled: false
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ },
+ 'TypeError'
+ );
+});
+
+g.test('render_bundle_encoder_descriptor_color_format').
+desc(
+ `
+ Test creating a render bundle encoder with an optional texture format set as one of the color
+ format will fail if the required optional feature is not enabled.
+
+ Note: This test has no cases if there are no optional texture formats supporting color rendering.
+ `
+).
+params((u) =>
+u.
+combine('format', kOptionalTextureFormats).
+filter((t) => !!kTextureFormatInfo[t.format].colorRender).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: [format]
+ });
+ });
+});
+
+g.test('render_bundle_encoder_descriptor_depth_stencil_format').
+desc(
+ `
+ Test creating a render bundle encoder with an optional texture format set as the depth stencil
+ format will fail if the required optional feature is not enabled.
+ `
+).
+params((u) =>
+u.
+combine('format', kOptionalTextureFormats).
+filter((t) => !!(kTextureFormatInfo[t.format].depth || kTextureFormatInfo[t.format].stencil)).
+combine('enable_required_feature', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ const formatInfo = kTextureFormatInfo[format];
+ if (enable_required_feature) {
+ t.selectDeviceOrSkipTestCase(formatInfo.feature);
+ }
+}).
+fn((t) => {
+ const { format, enable_required_feature } = t.params;
+
+ t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm'],
+ depthStencilFormat: format
+ });
+ });
+});
+
+g.test('check_capability_guarantees').
+desc(
+ `check "texture-compression-bc" is supported or both "texture-compression-etc2" and "texture-compression-astc" are supported.`
+).
+fn(async (t) => {
+ const adapter = await getGPU(t.rec).requestAdapter();
+ assert(adapter !== null);
+
+ const features = adapter.features;
+ t.expect(
+ features.has('texture-compression-bc') ||
+ features.has('texture-compression-etc2') && features.has('texture-compression-astc')
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/limit_utils.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/limit_utils.js
new file mode 100644
index 0000000000..10cfffdcef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/limit_utils.js
@@ -0,0 +1,1089 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kUnitCaseParamsBuilder } from '../../../../../common/framework/params_builder.js';import { makeTestGroup } from '../../../../../common/framework/test_group.js';import { getGPU } from '../../../../../common/util/navigator_gpu.js';
+import { assert, range, reorder } from '../../../../../common/util/util.js';
+import { getDefaultLimitsForAdapter } from '../../../../capability_info.js';
+import { GPUTestBase } from '../../../../gpu_test.js';
+
+
+
+export const kCreatePipelineTypes = [
+'createRenderPipeline',
+'createRenderPipelineWithFragmentStage',
+'createComputePipeline'];
+
+
+
+export const kRenderEncoderTypes = ['render', 'renderBundle'];
+
+
+export const kEncoderTypes = ['compute', 'render', 'renderBundle'];
+
+
+export const kBindGroupTests = ['sameGroup', 'differentGroups'];
+
+
+export const kBindingCombinations = [
+'vertex',
+'fragment',
+'vertexAndFragmentWithPossibleVertexStageOverflow',
+'vertexAndFragmentWithPossibleFragmentStageOverflow',
+'compute'];
+
+
+
+export function getPipelineTypeForBindingCombination(bindingCombination) {
+ switch (bindingCombination) {
+ case 'vertex':
+ return 'createRenderPipeline';
+ case 'fragment':
+ case 'vertexAndFragmentWithPossibleVertexStageOverflow':
+ case 'vertexAndFragmentWithPossibleFragmentStageOverflow':
+ return 'createRenderPipelineWithFragmentStage';
+ case 'compute':
+ return 'createComputePipeline';
+ }
+}
+
+function getBindGroupIndex(bindGroupTest, i) {
+ switch (bindGroupTest) {
+ case 'sameGroup':
+ return 0;
+ case 'differentGroups':
+ return i % 3;
+ }
+}
+
+function getWGSLBindings(
+order,
+bindGroupTest,
+storageDefinitionWGSLSnippetFn,
+numBindings,
+id)
+{
+ return reorder(
+ order,
+ range(
+ numBindings,
+ (i) =>
+ `@group(${getBindGroupIndex(
+ bindGroupTest,
+ i
+ )}) @binding(${i}) ${storageDefinitionWGSLSnippetFn(i, id)};`
+ )
+ ).join('\n ');
+}
+
+export function getPerStageWGSLForBindingCombinationImpl(
+bindingCombination,
+order,
+bindGroupTest,
+storageDefinitionWGSLSnippetFn,
+bodyFn,
+numBindings,
+extraWGSL = '')
+{
+ switch (bindingCombination) {
+ case 'vertex':
+ return `
+ ${extraWGSL}
+
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ ${bodyFn(numBindings, 0)}
+ return vec4f(0);
+ }
+ `;
+ case 'fragment':
+ return `
+ ${extraWGSL}
+
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+
+ @fragment fn mainFS() {
+ ${bodyFn(numBindings, 0)}
+ }
+ `;
+ case 'vertexAndFragmentWithPossibleVertexStageOverflow':{
+ return `
+ ${extraWGSL}
+
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings - 1, 1)}
+
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ ${bodyFn(numBindings, 0)}
+ return vec4f(0);
+ }
+
+ @fragment fn mainFS() {
+ ${bodyFn(numBindings - 1, 1)}
+ }
+ `;
+ }
+ case 'vertexAndFragmentWithPossibleFragmentStageOverflow':{
+ return `
+ ${extraWGSL}
+
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings - 1, 0)}
+
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 1)}
+
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ ${bodyFn(numBindings - 1, 0)}
+ return vec4f(0);
+ }
+
+ @fragment fn mainFS() {
+ ${bodyFn(numBindings, 1)}
+ }
+ `;
+ }
+ case 'compute':
+ return `
+ ${extraWGSL}
+ ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ @group(3) @binding(0) var<storage, read_write> d: f32;
+ @compute @workgroup_size(1) fn main() {
+ ${bodyFn(numBindings, 0)}
+ }
+ `;
+ break;
+ }
+}
+
+export function getPerStageWGSLForBindingCombination(
+bindingCombination,
+order,
+bindGroupTest,
+storageDefinitionWGSLSnippetFn,
+usageWGSLSnippetFn,
+numBindings,
+extraWGSL = '')
+{
+ return getPerStageWGSLForBindingCombinationImpl(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ storageDefinitionWGSLSnippetFn,
+ (numBindings, set) =>
+ `${range(numBindings, (i) => usageWGSLSnippetFn(i, set)).join('\n ')}`,
+ numBindings,
+ extraWGSL
+ );
+}
+
+export function getPerStageWGSLForBindingCombinationStorageTextures(
+bindingCombination,
+order,
+bindGroupTest,
+storageDefinitionWGSLSnippetFn,
+usageWGSLSnippetFn,
+numBindings,
+extraWGSL = '')
+{
+ return getPerStageWGSLForBindingCombinationImpl(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ storageDefinitionWGSLSnippetFn,
+ (numBindings, set) =>
+ `${range(numBindings, (i) => usageWGSLSnippetFn(i, set)).join('\n ')}`,
+ numBindings,
+ extraWGSL
+ );
+}
+
+export const kLimitModes = ['defaultLimit', 'adapterLimit'];
+
+
+
+export const kMaximumTestValues = ['atLimit', 'overLimit'];
+
+
+export function getMaximumTestValue(limit, testValue) {
+ switch (testValue) {
+ case 'atLimit':
+ return limit;
+ case 'overLimit':
+ return limit + 1;
+ }
+}
+
+export const kMinimumTestValues = ['atLimit', 'underLimit'];
+
+
+export const kMaximumLimitValueTests = [
+'atDefault',
+'underDefault',
+'betweenDefaultAndMaximum',
+'atMaximum',
+'overMaximum'];
+
+
+
+export function getLimitValue(
+defaultLimit,
+maximumLimit,
+limitValueTest)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'underDefault':
+ return defaultLimit - 1;
+ case 'betweenDefaultAndMaximum':
+ // The result can be larger than maximum i32.
+ return Math.floor((defaultLimit + maximumLimit) / 2);
+ case 'atMaximum':
+ return maximumLimit;
+ case 'overMaximum':
+ return maximumLimit + 1;
+ }
+}
+
+export const kMinimumLimitValueTests = [
+'atDefault',
+'overDefault',
+'betweenDefaultAndMinimum',
+'atMinimum',
+'underMinimum'];
+
+
+
+export function getDefaultLimitForAdapter(adapter, limit) {
+ const limitInfo = getDefaultLimitsForAdapter(adapter);
+ return limitInfo[limit].default;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+const kMinimumLimits = new Set([
+'minUniformBufferOffsetAlignment',
+'minStorageBufferOffsetAlignment']
+);
+
+/**
+ * Adds the default parameters to a limit test
+ */
+export const kMaximumLimitBaseParams = kUnitCaseParamsBuilder.
+combine('limitTest', kMaximumLimitValueTests).
+combine('testValueName', kMaximumTestValues);
+
+export const kMinimumLimitBaseParams = kUnitCaseParamsBuilder.
+combine('limitTest', kMinimumLimitValueTests).
+combine('testValueName', kMinimumTestValues);
+
+export class LimitTestsImpl extends GPUTestBase {
+ _adapter = null;
+ _device = undefined;
+ limit = '';
+ defaultLimit = 0;
+ adapterLimit = 0;
+
+ async init() {
+ await super.init();
+ const gpu = getGPU(this.rec);
+ this._adapter = await gpu.requestAdapter();
+ const limit = this.limit;
+ this.defaultLimit = getDefaultLimitForAdapter(this.adapter, limit);
+ this.adapterLimit = this.adapter.limits[limit];
+ assert(!Number.isNaN(this.defaultLimit));
+ assert(!Number.isNaN(this.adapterLimit));
+ }
+
+ get adapter() {
+ assert(this._adapter !== undefined);
+ return this._adapter;
+ }
+
+ get device() {
+ assert(this._device !== undefined, 'device is only valid in _testThenDestroyDevice callback');
+ return this._device;
+ }
+
+ async requestDeviceWithLimits(
+ adapter,
+ requiredLimits,
+ shouldReject,
+ requiredFeatures)
+ {
+ if (shouldReject) {
+ this.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }), {
+ allowMissingStack: true
+ });
+ return undefined;
+ } else {
+ return await adapter.requestDevice({ requiredLimits, requiredFeatures });
+ }
+ }
+
+ getDefaultOrAdapterLimit(limit, limitMode) {
+ switch (limitMode) {
+ case 'defaultLimit':
+ return getDefaultLimitForAdapter(this.adapter, limit);
+ case 'adapterLimit':
+ return this.adapter.limits[limit];
+ }
+ }
+
+ /**
+ * Gets a device with the adapter a requested limit and checks that that limit
+ * is correct or that the device failed to create if the requested limit is
+ * beyond the maximum supported by the device.
+ */
+ async _getDeviceWithSpecificLimit(
+ requestedLimit,
+ extraLimits,
+ features)
+ {
+ const { adapter, limit, adapterLimit, defaultLimit } = this;
+
+ const requiredLimits = {};
+ requiredLimits[limit] = requestedLimit;
+
+ if (extraLimits) {
+ for (const [extraLimitStr, limitMode] of Object.entries(extraLimits)) {
+ const extraLimit = extraLimitStr;
+ requiredLimits[extraLimit] =
+ limitMode === 'defaultLimit' ?
+ getDefaultLimitForAdapter(adapter, extraLimit) :
+ adapter.limits[extraLimit];
+ }
+ }
+
+ const shouldReject = kMinimumLimits.has(limit) ?
+ requestedLimit < adapterLimit :
+ requestedLimit > adapterLimit;
+
+ const device = await this.requestDeviceWithLimits(
+ adapter,
+ requiredLimits,
+ shouldReject,
+ features
+ );
+ const actualLimit = device ? device.limits[limit] : 0;
+
+ if (shouldReject) {
+ this.expect(!device, 'expected no device');
+ } else {
+ if (kMinimumLimits.has(limit)) {
+ if (requestedLimit <= defaultLimit) {
+ this.expect(
+ actualLimit === requestedLimit,
+ `expected actual actualLimit: ${actualLimit} to equal defaultLimit: ${requestedLimit}`
+ );
+ } else {
+ this.expect(
+ actualLimit === defaultLimit,
+ `expected actual actualLimit: ${actualLimit} to equal defaultLimit: ${defaultLimit}`
+ );
+ }
+ } else {
+ if (requestedLimit <= defaultLimit) {
+ this.expect(
+ actualLimit === defaultLimit,
+ `expected actual actualLimit: ${actualLimit} to equal defaultLimit: ${defaultLimit}`
+ );
+ } else {
+ this.expect(
+ actualLimit === requestedLimit,
+ `expected actual actualLimit: ${actualLimit} to equal requestedLimit: ${requestedLimit}`
+ );
+ }
+ }
+ }
+
+ return device ? { device, defaultLimit, adapterLimit, requestedLimit, actualLimit } : undefined;
+ }
+
+ /**
+ * Gets a device with the adapter a requested limit and checks that that limit
+ * is correct or that the device failed to create if the requested limit is
+ * beyond the maximum supported by the device.
+ */
+ async _getDeviceWithRequestedMaximumLimit(
+ limitValueTest,
+ extraLimits,
+ features)
+ {
+ const { defaultLimit, adapterLimit: maximumLimit } = this;
+
+ const requestedLimit = getLimitValue(defaultLimit, maximumLimit, limitValueTest);
+ return this._getDeviceWithSpecificLimit(requestedLimit, extraLimits, features);
+ }
+
+ /**
+ * Call the given function and check no WebGPU errors are leaked.
+ */
+ async _testThenDestroyDevice(
+ deviceAndLimits,
+ testValue,
+ fn)
+ {
+ assert(!this._device);
+
+ const { device, actualLimit } = deviceAndLimits;
+ this._device = device;
+
+ const shouldError = kMinimumLimits.has(this.limit) ?
+ testValue < actualLimit :
+ testValue > actualLimit;
+
+ device.pushErrorScope('internal');
+ device.pushErrorScope('out-of-memory');
+ device.pushErrorScope('validation');
+
+ await fn({ ...deviceAndLimits, testValue, shouldError });
+
+ const validationError = await device.popErrorScope();
+ const outOfMemoryError = await device.popErrorScope();
+ const internalError = await device.popErrorScope();
+
+ this.expect(!validationError, `unexpected validation error: ${validationError?.message || ''}`);
+ this.expect(
+ !outOfMemoryError,
+ `unexpected out-of-memory error: ${outOfMemoryError?.message || ''}`
+ );
+ this.expect(!internalError, `unexpected internal error: ${internalError?.message || ''}`);
+
+ device.destroy();
+ this._device = undefined;
+ }
+
+ /**
+ * Creates a device with a specific limit.
+ * If the limit of over the maximum we expect an exception
+ * If the device is created then we call a test function, checking
+ * that the function does not leak any GPU errors.
+ */
+ async testDeviceWithSpecificLimits(
+ deviceLimitValue,
+ testValue,
+ fn,
+ extraLimits,
+ features)
+ {
+ assert(!this._device);
+
+ const deviceAndLimits = await this._getDeviceWithSpecificLimit(
+ deviceLimitValue,
+ extraLimits,
+ features
+ );
+ // If we request over the limit requestDevice will throw
+ if (!deviceAndLimits) {
+ return;
+ }
+
+ await this._testThenDestroyDevice(deviceAndLimits, testValue, fn);
+ }
+
+ /**
+ * Creates a device with the limit defined by LimitValueTest.
+ * If the limit of over the maximum we expect an exception
+ * If the device is created then we call a test function, checking
+ * that the function does not leak any GPU errors.
+ */
+ async testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ fn,
+ extraLimits)
+ {
+ assert(!this._device);
+
+ const deviceAndLimits = await this._getDeviceWithRequestedMaximumLimit(limitTest, extraLimits);
+ // If we request over the limit requestDevice will throw
+ if (!deviceAndLimits) {
+ return;
+ }
+
+ const { actualLimit } = deviceAndLimits;
+ const testValue = getMaximumTestValue(actualLimit, testValueName);
+
+ await this._testThenDestroyDevice(
+ deviceAndLimits,
+ testValue,
+ async (inputs) => {
+ await fn({ ...inputs, testValueName });
+ }
+ );
+ }
+
+ /**
+ * Calls a function that expects a GPU error if shouldError is true
+ */
+ // MAINTENANCE_TODO: Remove this duplicated code with GPUTest if possible
+ async expectGPUErrorAsync(
+ filter,
+ fn,
+ shouldError = true,
+ msg = '')
+ {
+ const { device } = this;
+
+ device.pushErrorScope(filter);
+ const returnValue = fn();
+ if (returnValue instanceof Promise) {
+ await returnValue;
+ }
+
+ const error = await device.popErrorScope();
+ this.expect(
+ !!error === shouldError,
+ `${error?.message || 'no error when one was expected'}: ${msg}`
+ );
+
+ return returnValue;
+ }
+
+ /** Expect that the provided promise rejects, with the provided exception name. */
+ async shouldRejectConditionally(
+ expectedName,
+ p,
+ shouldReject,
+ message)
+ {
+ if (shouldReject) {
+ this.shouldReject(expectedName, p, { message });
+ } else {
+ this.shouldResolve(p, message);
+ }
+
+ // We need to explicitly wait for the promise because the device may be
+ // destroyed immediately after returning from this function.
+ try {
+ await p;
+ } catch (e) {
+
+ //
+ }}
+
+ /**
+ * Calls a function that expects a validation error if shouldError is true
+ */
+ async expectValidationError(
+ fn,
+ shouldError = true,
+ msg = '')
+ {
+ return this.expectGPUErrorAsync('validation', fn, shouldError, msg);
+ }
+
+ /**
+ * Calls a function that expects to not generate a validation error
+ */
+ async expectNoValidationError(fn, msg = '') {
+ return this.expectGPUErrorAsync('validation', fn, false, msg);
+ }
+
+ /**
+ * Calls a function that might expect a validation error.
+ * if shouldError is true then expect a validation error,
+ * if shouldError is false then ignore out-of-memory errors.
+ */
+ async testForValidationErrorWithPossibleOutOfMemoryError(
+ fn,
+ shouldError = true,
+ msg = '')
+ {
+ const { device } = this;
+
+ if (!shouldError) {
+ device.pushErrorScope('out-of-memory');
+ const result = fn();
+ await device.popErrorScope();
+ return result;
+ }
+
+ // Validation should fail before out-of-memory so there is no need to check
+ // for out-of-memory here.
+ device.pushErrorScope('validation');
+ const returnValue = fn();
+ const validationError = await device.popErrorScope();
+
+ this.expect(
+ !!validationError,
+ `${validationError?.message || 'no error when one was expected'}: ${msg}`
+ );
+
+ return returnValue;
+ }
+
+ getGroupIndexWGSLForPipelineType(pipelineType, groupIndex) {
+ switch (pipelineType) {
+ case 'createRenderPipeline':
+ return `
+ @group(${groupIndex}) @binding(0) var<uniform> v: f32;
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ return vec4f(v);
+ }
+ `;
+ case 'createRenderPipelineWithFragmentStage':
+ return `
+ @group(${groupIndex}) @binding(0) var<uniform> v: f32;
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ return vec4f(v);
+ }
+ @fragment fn mainFS() -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ `;
+ case 'createComputePipeline':
+ return `
+ @group(${groupIndex}) @binding(0) var<uniform> v: f32;
+ @compute @workgroup_size(1) fn main() {
+ _ = v;
+ }
+ `;
+ break;
+ }
+ }
+
+ getBindingIndexWGSLForPipelineType(pipelineType, bindingIndex) {
+ switch (pipelineType) {
+ case 'createRenderPipeline':
+ return `
+ @group(0) @binding(${bindingIndex}) var<uniform> v: f32;
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ return vec4f(v);
+ }
+ `;
+ case 'createRenderPipelineWithFragmentStage':
+ return `
+ @group(0) @binding(${bindingIndex}) var<uniform> v: f32;
+ @vertex fn mainVS() -> @builtin(position) vec4f {
+ return vec4f(v);
+ }
+ @fragment fn mainFS() -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ `;
+ case 'createComputePipeline':
+ return `
+ @group(0) @binding(${bindingIndex}) var<uniform> v: f32;
+ @compute @workgroup_size(1) fn main() {
+ _ = v;
+ }
+ `;
+ break;
+ }
+ }
+
+ _createRenderPipelineDescriptor(module) {
+ return {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'mainVS'
+ }
+ };
+ }
+
+ _createRenderPipelineDescriptorWithFragmentShader(
+ module)
+ {
+ return {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'mainVS'
+ },
+ fragment: {
+ module,
+ entryPoint: 'mainFS',
+ targets: []
+ },
+ depthStencil: {
+ format: 'depth24plus-stencil8',
+ depthWriteEnabled: true,
+ depthCompare: 'always'
+ }
+ };
+ }
+
+ _createComputePipelineDescriptor(module) {
+ return {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main'
+ }
+ };
+ }
+
+ createPipeline(createPipelineType, module) {
+ const { device } = this;
+
+ switch (createPipelineType) {
+ case 'createRenderPipeline':
+ return device.createRenderPipeline(this._createRenderPipelineDescriptor(module));
+ break;
+ case 'createRenderPipelineWithFragmentStage':
+ return device.createRenderPipeline(
+ this._createRenderPipelineDescriptorWithFragmentShader(module)
+ );
+ break;
+ case 'createComputePipeline':
+ return device.createComputePipeline(this._createComputePipelineDescriptor(module));
+ break;
+ }
+ }
+
+ createPipelineAsync(createPipelineType, module) {
+ const { device } = this;
+
+ switch (createPipelineType) {
+ case 'createRenderPipeline':
+ return device.createRenderPipelineAsync(this._createRenderPipelineDescriptor(module));
+ case 'createRenderPipelineWithFragmentStage':
+ return device.createRenderPipelineAsync(
+ this._createRenderPipelineDescriptorWithFragmentShader(module)
+ );
+ case 'createComputePipeline':
+ return device.createComputePipelineAsync(this._createComputePipelineDescriptor(module));
+ }
+ }
+
+ async testCreatePipeline(
+ createPipelineType,
+ async,
+ module,
+ shouldError,
+ msg = '')
+ {
+ if (async) {
+ await this.shouldRejectConditionally(
+ 'GPUPipelineError',
+ this.createPipelineAsync(createPipelineType, module),
+ shouldError,
+ msg
+ );
+ } else {
+ await this.expectValidationError(
+ () => {
+ this.createPipeline(createPipelineType, module);
+ },
+ shouldError,
+ msg
+ );
+ }
+ }
+
+ async testCreateRenderPipeline(
+ pipelineDescriptor,
+ async,
+ shouldError,
+ msg = '')
+ {
+ const { device } = this;
+ if (async) {
+ await this.shouldRejectConditionally(
+ 'GPUPipelineError',
+ device.createRenderPipelineAsync(pipelineDescriptor),
+ shouldError,
+ msg
+ );
+ } else {
+ await this.expectValidationError(
+ () => {
+ device.createRenderPipeline(pipelineDescriptor);
+ },
+ shouldError,
+ msg
+ );
+ }
+ }
+
+ async testMaxComputeWorkgroupSize(
+ limitTest,
+ testValueName,
+ async,
+ axis)
+ {
+ const kExtraLimits = {
+ maxComputeInvocationsPerWorkgroup: 'adapterLimit'
+ };
+
+ await this.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ if (testValue > device.limits.maxComputeInvocationsPerWorkgroup) {
+ return;
+ }
+
+ const size = [1, 1, 1];
+ size[axis.codePointAt(0) - 'X'.codePointAt(0)] = testValue;
+ const { module, code } = this.getModuleForWorkgroupSize(size);
+
+ await this.testCreatePipeline(
+ 'createComputePipeline',
+ async,
+ module,
+ shouldError,
+ `size: ${testValue}, limit: ${actualLimit}\n${code}`
+ );
+ },
+ kExtraLimits
+ );
+ }
+
+ /**
+ * Creates an GPURenderCommandsMixin setup with some initial state.
+ */
+ _getGPURenderCommandsMixin(encoderType) {
+ const { device } = this;
+
+ switch (encoderType) {
+ case 'render':{
+ const buffer = this.trackForCleanup(
+ device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.UNIFORM
+ })
+ );
+
+ const texture = this.trackForCleanup(
+ device.createTexture({
+ size: [1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer }
+ }]
+
+ });
+
+ const encoder = device.createCommandEncoder();
+ const mixin = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ return {
+ mixin,
+ bindGroup,
+ prep() {
+ mixin.end();
+ },
+ test() {
+ encoder.finish();
+ }
+ };
+ break;
+ }
+
+ case 'renderBundle':{
+ const buffer = this.trackForCleanup(
+ device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.UNIFORM
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer }
+ }]
+
+ });
+
+ const mixin = device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ });
+
+ return {
+ mixin,
+ bindGroup,
+ prep() {},
+ test() {
+ mixin.finish();
+ }
+ };
+ break;
+ }
+ }
+ }
+
+ /**
+ * Tests a method on GPURenderCommandsMixin
+ * The function will be called with the mixin.
+ */
+ async testGPURenderCommandsMixin(
+ encoderType,
+ fn,
+ shouldError,
+ msg = '')
+ {
+ const { mixin, prep, test } = this._getGPURenderCommandsMixin(encoderType);
+ fn({ mixin });
+ prep();
+
+ await this.expectValidationError(test, shouldError, msg);
+ }
+
+ /**
+ * Creates GPUBindingCommandsMixin setup with some initial state.
+ */
+ _getGPUBindingCommandsMixin(encoderType) {
+ const { device } = this;
+
+ switch (encoderType) {
+ case 'compute':{
+ const buffer = this.trackForCleanup(
+ device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.UNIFORM
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: {}
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer }
+ }]
+
+ });
+
+ const encoder = device.createCommandEncoder();
+ const mixin = encoder.beginComputePass();
+ return {
+ mixin,
+ bindGroup,
+ prep() {
+ mixin.end();
+ },
+ test() {
+ encoder.finish();
+ }
+ };
+ break;
+ }
+ case 'render':
+ return this._getGPURenderCommandsMixin('render');
+ case 'renderBundle':
+ return this._getGPURenderCommandsMixin('renderBundle');
+ }
+ }
+
+ /**
+ * Tests a method on GPUBindingCommandsMixin
+ * The function pass will be called with the mixin and a bindGroup
+ */
+ async testGPUBindingCommandsMixin(
+ encoderType,
+ fn,
+ shouldError,
+ msg = '')
+ {
+ const { mixin, bindGroup, prep, test } = this._getGPUBindingCommandsMixin(encoderType);
+ fn({ mixin, bindGroup });
+ prep();
+
+ await this.expectValidationError(test, shouldError, msg);
+ }
+
+ getModuleForWorkgroupSize(size) {
+ const { device } = this;
+ const code = `
+ @group(0) @binding(0) var<storage, read_write> d: f32;
+ @compute @workgroup_size(${size.join(',')}) fn main() {
+ d = 0;
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ return { module, code };
+ }
+}
+
+/**
+ * Makes a new LimitTest class so that the tests have access to `limit`
+ */
+function makeLimitTestFixture(limit) {
+ class LimitTests extends LimitTestsImpl {
+ limit = limit;
+ }
+
+ return LimitTests;
+}
+
+/**
+ * This is to avoid repeating yourself (D.R.Y.) as I ran into that issue multiple times
+ * writing these tests where I'd copy a test, need to rename a limit in 3-4 places,
+ * forget one place, and then spend 20-30 minutes wondering why the test was failing.
+ */
+export function makeLimitTestGroup(limit) {
+ const description = `API Validation Tests for ${limit}.`;
+ const g = makeTestGroup(makeLimitTestFixture(limit));
+ return { g, description, limit };
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.js
new file mode 100644
index 0000000000..ab0eaa3e4b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.js
@@ -0,0 +1,95 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../../../../common/util/util.js';import {
+ kCreatePipelineTypes,
+ kEncoderTypes,
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup } from
+'./limit_utils.js';
+
+const limit = 'maxBindGroups';
+export const { g, description } = makeLimitTestGroup(limit);
+
+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: {}
+ }]
+
+ })
+ );
+
+ await t.expectValidationError(() => {
+ device.createPipelineLayout({ bindGroupLayouts });
+ }, shouldError);
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit`
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('createPipelineType', kCreatePipelineTypes).
+ combine('async', [false, true])
+).
+fn(async (t) => {
+ const { limitTest, testValueName, createPipelineType, async } = t.params;
+
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const lastIndex = testValue - 1;
+
+ const code = t.getGroupIndexWGSLForPipelineType(createPipelineType, lastIndex);
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(createPipelineType, async, module, shouldError);
+ }
+ );
+});
+
+g.test('setBindGroup,at_over').
+desc(`Test using setBindGroup at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('encoderType', kEncoderTypes)).
+fn(async (t) => {
+ const { limitTest, testValueName, encoderType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ testValue, actualLimit, shouldError }) => {
+ const lastIndex = testValue - 1;
+ await t.testGPUBindingCommandsMixin(
+ encoderType,
+ ({ mixin, bindGroup }) => {
+ mixin.setBindGroup(lastIndex, bindGroup);
+ },
+ shouldError,
+ `shouldError: ${shouldError}, actualLimit: ${actualLimit}, testValue: ${lastIndex}`
+ );
+ }
+ );
+});
+
+g.test('validate,maxBindGroupsPlusVertexBuffers').
+desc(`Test that ${limit} <= maxBindGroupsPlusVertexBuffers`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxBindGroupsPlusVertexBuffers'));
+ t.expect(adapterLimit <= adapter.limits.maxBindGroupsPlusVertexBuffers);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup.spec.js
new file mode 100644
index 0000000000..5c5c6ebcb9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBindingsPerBindGroup.spec.js
@@ -0,0 +1,75 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kCreatePipelineTypes, kMaximumLimitBaseParams,
+ makeLimitTestGroup } from
+'./limit_utils.js';
+
+const limit = 'maxBindingsPerBindGroup';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBindGroupLayout,at_over').
+desc(`Test using createBindGroupLayout at and over ${limit} limit`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.expectValidationError(() => {
+ device.createBindGroupLayout({
+ entries: [
+ {
+ binding: testValue - 1,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+ }, shouldError);
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit`
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('createPipelineType', kCreatePipelineTypes).
+ combine('async', [false, true])
+).
+fn(async (t) => {
+ const { limitTest, testValueName, createPipelineType, async } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const lastIndex = testValue - 1;
+
+ const code = t.getBindingIndexWGSLForPipelineType(createPipelineType, lastIndex);
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(createPipelineType, async, module, shouldError, code);
+ }
+ );
+});
+
+g.test('validate').
+desc(`Test ${limit} matches the spec limits`).
+fn((t) => {
+ const { adapter, adapterLimit } = t;
+ const maxBindingsPerShaderStage =
+ adapter.limits.maxSampledTexturesPerShaderStage +
+ adapter.limits.maxSamplersPerShaderStage +
+ adapter.limits.maxStorageBuffersPerShaderStage +
+ adapter.limits.maxStorageTexturesPerShaderStage +
+ adapter.limits.maxUniformBuffersPerShaderStage;
+ const maxShaderStagesPerPipeline = 2;
+ const minMaxBindingsPerBindGroup = maxBindingsPerShaderStage * maxShaderStagesPerPipeline;
+ t.expect(
+ adapterLimit >= minMaxBindingsPerBindGroup,
+ `maxBindingsPerBindGroup(${adapterLimit}) >= maxBindingsPerShaderStage(${maxBindingsPerShaderStage}) * maxShaderStagesPerPipeline(${maxShaderStagesPerPipeline} = (${minMaxBindingsPerBindGroup}))`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBufferSize.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBufferSize.spec.js
new file mode 100644
index 0000000000..ab2d5215d5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxBufferSize.spec.js
@@ -0,0 +1,28 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxBufferSize';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBuffer,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(
+ () => {
+ const buffer = device.createBuffer({
+ usage: GPUBufferUsage.VERTEX,
+ size: testValue
+ });
+ buffer.destroy();
+ },
+ shouldError,
+ `size: ${testValue}, limit: ${actualLimit}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample.spec.js
new file mode 100644
index 0000000000..508b436dee
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachmentBytesPerSample.spec.js
@@ -0,0 +1,260 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../../../../common/util/util.js';import { kTextureSampleCounts } from '../../../../capability_info.js';import { kTextureFormatInfo } from '../../../../format_info.js';
+import { align } from '../../../../util/math.js';
+
+import {
+ kMaximumLimitBaseParams,
+
+
+ makeLimitTestGroup } from
+'./limit_utils.js';
+
+const kFormatsToUseBySize = [
+'rgba32float',
+'rgba16float',
+'rgba8unorm',
+'rg8unorm',
+'r8unorm'];
+
+
+const kInterleaveFormats = [
+'rgba16float',
+'rg16float',
+'rgba8unorm',
+'rg8unorm',
+'r8unorm'];
+
+
+function getAttachments(interleaveFormat, testValue) {
+ let bytesPerSample = 0;
+ const targets = [];
+
+ const addTexture = (format) => {
+ const info = kTextureFormatInfo[format];
+ const newBytesPerSample =
+ align(bytesPerSample, info.colorRender.alignment) + info.colorRender.byteCost;
+ if (newBytesPerSample > testValue) {
+ return false;
+ }
+ targets.push({ format, writeMask: 0 });
+ bytesPerSample = newBytesPerSample;
+ return true;
+ };
+
+ while (bytesPerSample < testValue) {
+ addTexture(interleaveFormat);
+ for (const format of kFormatsToUseBySize) {
+ if (addTexture(format)) {
+ break;
+ }
+ }
+ }
+
+ assert(bytesPerSample === testValue);
+ return targets;
+}
+
+function getDescription(
+testValue,
+actualLimit,
+sampleCount,
+targets)
+{
+ return `
+ // testValue : ${testValue}
+ // actualLimit: ${actualLimit}
+ // sampleCount: ${sampleCount}
+ // targets:
+ ${(() => {
+ let offset = 0;
+ return targets.
+ map(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ offset = align(offset, info.colorRender.alignment);
+ const s = `// ${format.padEnd(11)} (offset: ${offset.toString().padStart(2)}, align: ${
+ info.colorRender.alignment
+ }, size: ${info.colorRender.byteCost})`;
+ offset += info.colorRender.byteCost;
+ return s;
+ }).
+ join('\n ');
+ })()}
+ `;
+}
+
+function getPipelineDescriptor(
+device,
+actualLimit,
+interleaveFormat,
+sampleCount,
+testValue)
+{
+ const targets = getAttachments(interleaveFormat, testValue);
+ if (!targets) {
+ return;
+ }
+
+ const code = `
+ ${getDescription(testValue, actualLimit, sampleCount, targets)}
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ const pipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets
+ },
+ // depth should not affect the test so added to make sure the implementation does not consider it
+ depthStencil: {
+ depthWriteEnabled: true,
+ depthCompare: 'less',
+ format: 'depth24plus'
+ },
+ multisample: {
+ count: sampleCount
+ }
+ };
+ return { pipelineDescriptor, code };
+}
+
+function createTextures(t, targets) {
+ return targets.map(({ format }) =>
+ t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1],
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ )
+ );
+}
+
+const kExtraLimits = {
+ maxColorAttachments: 'adapterLimit'
+};
+
+const limit = 'maxColorAttachmentBytesPerSample';
+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('sampleCount', kTextureSampleCounts).
+ combine('interleaveFormat', kInterleaveFormats)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, sampleCount, interleaveFormat } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const result = getPipelineDescriptor(
+ device,
+ actualLimit,
+ interleaveFormat,
+ sampleCount,
+ testValue
+ );
+ if (!result) {
+ return;
+ }
+ const { pipelineDescriptor, code } = result;
+ const numTargets = pipelineDescriptor.fragment.targets.length;
+ if (numTargets > device.limits.maxColorAttachments) {
+ return;
+ }
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError, code);
+ },
+ kExtraLimits
+ );
+});
+
+g.test('beginRenderPass,at_over').
+desc(`Test using at and over ${limit} limit in beginRenderPass`).
+params(
+ kMaximumLimitBaseParams.
+ combine('sampleCount', kTextureSampleCounts).
+ combine('interleaveFormat', kInterleaveFormats)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, sampleCount, interleaveFormat } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const targets = getAttachments(interleaveFormat, testValue);
+ if (targets.length > device.limits.maxColorAttachments) {
+ return;
+ }
+
+ const encoder = device.createCommandEncoder();
+ const textures = createTextures(t, targets);
+
+ const pass = encoder.beginRenderPass({
+ colorAttachments: textures.map((texture) => ({
+ view: texture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }))
+ });
+ pass.end();
+
+ await t.expectValidationError(
+ () => {
+ encoder.finish();
+ },
+ shouldError,
+ getDescription(testValue, actualLimit, sampleCount, targets)
+ );
+ },
+ kExtraLimits
+ );
+});
+
+g.test('createRenderBundle,at_over').
+desc(`Test using at and over ${limit} limit in createRenderBundle`).
+params(
+ kMaximumLimitBaseParams.
+ combine('sampleCount', kTextureSampleCounts).
+ combine('interleaveFormat', kInterleaveFormats)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, sampleCount, interleaveFormat } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const targets = getAttachments(interleaveFormat, testValue);
+ if (targets.length > device.limits.maxColorAttachments) {
+ return;
+ }
+
+ await t.expectValidationError(
+ () => {
+ device.createRenderBundleEncoder({
+ colorFormats: targets.map(({ format }) => format)
+ });
+ },
+ shouldError,
+ getDescription(testValue, actualLimit, sampleCount, targets)
+ );
+ },
+ kExtraLimits
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachments.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachments.spec.js
new file mode 100644
index 0000000000..80fa8fdd7c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxColorAttachments.spec.js
@@ -0,0 +1,124 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../../../../common/util/util.js';import { kMaxColorAttachmentsToTest } from '../../../../capability_info.js';
+import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
+
+function getPipelineDescriptor(device, testValue) {
+ const code = `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ return {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: new Array(testValue).fill({ format: 'r8unorm', writeMask: 0 })
+ }
+ };
+}
+
+const limit = 'maxColorAttachments';
+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])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const pipelineDescriptor = getPipelineDescriptor(device, testValue);
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ }
+ );
+});
+
+g.test('beginRenderPass,at_over').
+desc(`Test using at and over ${limit} limit in beginRenderPass`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const encoder = device.createCommandEncoder();
+
+ const textures = range(testValue, (_) =>
+ t.trackForCleanup(
+ device.createTexture({
+ size: [1, 1],
+ format: 'r8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ )
+ );
+
+ const pass = encoder.beginRenderPass({
+ colorAttachments: range(testValue, (i) => ({
+ view: textures[i].createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }))
+ });
+ pass.end();
+
+ await t.expectValidationError(() => {
+ encoder.finish();
+ }, shouldError);
+ }
+ );
+});
+
+g.test('createRenderBundle,at_over').
+desc(`Test using at and over ${limit} limit in createRenderBundle`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.expectValidationError(() => {
+ device.createRenderBundleEncoder({
+ colorFormats: new Array(testValue).fill('r8unorm')
+ });
+ }, shouldError);
+ }
+ );
+});
+
+g.test('validate,maxColorAttachmentBytesPerSample').
+desc(`Test ${limit} against maxColorAttachmentBytesPerSample`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit: maximumLimit } = t;
+ const minColorAttachmentBytesPerSample = t.getDefaultLimit('maxColorAttachmentBytesPerSample');
+ // The smallest attachment is 1 byte
+ // so make sure maxColorAttachments < maxColorAttachmentBytesPerSample
+ t.expect(defaultLimit <= minColorAttachmentBytesPerSample);
+ t.expect(maximumLimit <= adapter.limits.maxColorAttachmentBytesPerSample);
+});
+
+g.test('validate,kMaxColorAttachmentsToTest').
+desc(
+ `
+ Tests that kMaxColorAttachmentsToTest is large enough to test the limits of this device
+ `
+).
+fn((t) => {
+ t.expect(t.adapter.limits.maxColorAttachments <= kMaxColorAttachmentsToTest);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup.spec.js
new file mode 100644
index 0000000000..6a00ba5805
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeInvocationsPerWorkgroup.spec.js
@@ -0,0 +1,147 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import {
+ kMaximumLimitBaseParams,
+
+
+ makeLimitTestGroup } from
+'./limit_utils.js';
+
+/**
+ * Given a 3 dimensional size, and a limit, compute
+ * the smallest volume with more then limit units.
+ */
+function getClosestSizeOverLimit(size, limit) {
+ let closest = Number.MAX_SAFE_INTEGER;
+ let closestSize = [];
+ const depthLimit = Math.min(limit, size[2]);
+ for (let depth = 1; depth <= depthLimit; ++depth) {
+ for (let height = 1; height <= size[1]; ++height) {
+ const planeSize = depth * height;
+ if (planeSize <= limit) {
+ const width = Math.min(size[0], Math.ceil(limit / planeSize));
+ const num = width * planeSize;
+ const dist = num - limit;
+ if (dist > 0 && dist < closest) {
+ closest = dist;
+ closestSize = [width, height, depth];
+ }
+ }
+ }
+ }
+ return closestSize;
+}
+
+/**
+ * Given a 3 dimensional size, and a limit, compute
+ * the largest volume with limit or less units.
+ */
+function getClosestSizeUnderOrAtLimit(size, limit) {
+ let closest = Number.MAX_SAFE_INTEGER;
+ let closestSize = [];
+ const depthLimit = Math.min(limit, size[2]);
+ for (let depth = 1; depth <= depthLimit; ++depth) {
+ for (let height = 1; height <= size[1]; ++height) {
+ const planeSize = depth * height;
+ if (planeSize <= limit) {
+ const width = Math.min(size[0], Math.floor(limit / planeSize));
+ const num = width * planeSize;
+ const dist = limit - num;
+ if (dist < closest) {
+ closest = dist;
+ closestSize = [width, height, depth];
+ }
+ }
+ }
+ }
+ return closestSize;
+}
+
+function getDeviceLimitToRequest(
+limitValueTest,
+defaultLimit,
+maximumLimit)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'underDefault':
+ return defaultLimit - 1;
+ case 'betweenDefaultAndMaximum':
+ return Math.floor((defaultLimit + maximumLimit) / 2);
+ case 'atMaximum':
+ return maximumLimit;
+ case 'overMaximum':
+ return maximumLimit + 1;
+ }
+}
+
+function getTestWorkgroupSize(
+t,
+testValueName,
+requestedLimit)
+{
+ const maxDimensions = [
+ t.getDefaultLimit('maxComputeWorkgroupSizeX'),
+ t.getDefaultLimit('maxComputeWorkgroupSizeY'),
+ t.getDefaultLimit('maxComputeWorkgroupSizeZ')];
+
+
+ switch (testValueName) {
+ case 'atLimit':
+ return getClosestSizeUnderOrAtLimit(maxDimensions, requestedLimit);
+ case 'overLimit':
+ return getClosestSizeOverLimit(maxDimensions, requestedLimit);
+ }
+}
+
+function getDeviceLimitToRequestAndValueToTest(
+t,
+limitValueTest,
+testValueName,
+defaultLimit,
+maximumLimit)
+{
+ const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
+ const workgroupSize = getTestWorkgroupSize(t, testValueName, requestedLimit);
+ return {
+ requestedLimit,
+ workgroupSize
+ };
+}
+
+const limit = 'maxComputeInvocationsPerWorkgroup';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createComputePipeline,at_over').
+desc(`Test using createComputePipeline(Async) at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('async', [false, true])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ const { defaultLimit, adapterLimit: maximumLimit } = t;
+
+ const { requestedLimit, workgroupSize } = getDeviceLimitToRequestAndValueToTest(
+ t,
+ limitTest,
+ testValueName,
+ defaultLimit,
+ maximumLimit
+ );
+ const testValue = workgroupSize.reduce((a, b) => a * b, 1);
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ testValue, actualLimit, shouldError }) => {
+ const { module, code } = t.getModuleForWorkgroupSize(workgroupSize);
+
+ await t.testCreatePipeline(
+ 'createComputePipeline',
+ async,
+ module,
+ shouldError,
+ `workgroupSize: [${workgroupSize}], size: ${testValue}, limit: ${actualLimit}\n${code}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX.spec.js
new file mode 100644
index 0000000000..7eaf1e749b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeX.spec.js
@@ -0,0 +1,20 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxComputeWorkgroupSizeX';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createComputePipeline,at_over').
+desc(`Test using createComputePipeline(Async) at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('async', [false, true])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'X');
+});
+
+g.test('validate,maxComputeInvocationsPerWorkgroup').
+desc(`Test that ${limit} <= maxComputeInvocationsPerWorkgroup`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxComputeInvocationsPerWorkgroup'));
+ t.expect(adapterLimit <= adapter.limits.maxComputeInvocationsPerWorkgroup);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY.spec.js
new file mode 100644
index 0000000000..5a24d646c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeY.spec.js
@@ -0,0 +1,20 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxComputeWorkgroupSizeY';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createComputePipeline,at_over').
+desc(`Test using createComputePipeline(Async) at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('async', [false, true])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'Y');
+});
+
+g.test('validate,maxComputeInvocationsPerWorkgroup').
+desc(`Test that ${limit} <= maxComputeInvocationsPerWorkgroup`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxComputeInvocationsPerWorkgroup'));
+ t.expect(adapterLimit <= adapter.limits.maxComputeInvocationsPerWorkgroup);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ.spec.js
new file mode 100644
index 0000000000..dbd035de10
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupSizeZ.spec.js
@@ -0,0 +1,20 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxComputeWorkgroupSizeZ';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createComputePipeline,at_over').
+desc(`Test using createComputePipeline(Async) at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('async', [false, true])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ await t.testMaxComputeWorkgroupSize(limitTest, testValueName, async, 'Z');
+});
+
+g.test('validate,maxComputeInvocationsPerWorkgroup').
+desc(`Test that ${limit} <= maxComputeInvocationsPerWorkgroup`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxComputeInvocationsPerWorkgroup'));
+ t.expect(adapterLimit <= adapter.limits.maxComputeInvocationsPerWorkgroup);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.js
new file mode 100644
index 0000000000..2034364a5f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.js
@@ -0,0 +1,182 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { keysOf } from '../../../../../common/util/data_tables.js';import { assert } from '../../../../../common/util/util.js';import { align, roundDown } from '../../../../util/math.js';
+
+import {
+
+
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup } from
+'./limit_utils.js';
+
+const limit = 'maxComputeWorkgroupStorageSize';
+export const { g, description } = makeLimitTestGroup(limit);
+
+const kSmallestWorkgroupVarSize = 4;
+
+const wgslF16Types = {
+ f16: { alignOf: 2, sizeOf: 2, requireF16: true },
+ 'vec2<f16>': { alignOf: 4, sizeOf: 4, requireF16: true },
+ 'vec3<f16>': { alignOf: 8, sizeOf: 6, requireF16: true },
+ 'vec4<f16>': { alignOf: 8, sizeOf: 8, requireF16: true },
+ 'mat2x2<f16>': { alignOf: 4, sizeOf: 8, requireF16: true },
+ 'mat3x2<f16>': { alignOf: 4, sizeOf: 12, requireF16: true },
+ 'mat4x2<f16>': { alignOf: 4, sizeOf: 16, requireF16: true },
+ 'mat2x3<f16>': { alignOf: 8, sizeOf: 16, requireF16: true },
+ 'mat3x3<f16>': { alignOf: 8, sizeOf: 24, requireF16: true },
+ 'mat4x3<f16>': { alignOf: 8, sizeOf: 32, requireF16: true },
+ 'mat2x4<f16>': { alignOf: 8, sizeOf: 16, requireF16: true },
+ 'mat3x4<f16>': { alignOf: 8, sizeOf: 24, requireF16: true },
+ 'mat4x4<f16>': { alignOf: 8, sizeOf: 32, requireF16: true }
+};
+
+const wgslBaseTypes = {
+ f32: { alignOf: 4, sizeOf: 4, requireF16: false },
+ i32: { alignOf: 4, sizeOf: 4, requireF16: false },
+ u32: { alignOf: 4, sizeOf: 4, requireF16: false },
+
+ 'vec2<f32>': { alignOf: 8, sizeOf: 8, requireF16: false },
+ 'vec2<i32>': { alignOf: 8, sizeOf: 8, requireF16: false },
+ 'vec2<u32>': { alignOf: 8, sizeOf: 8, requireF16: false },
+
+ 'vec3<f32>': { alignOf: 16, sizeOf: 12, requireF16: false },
+ 'vec3<i32>': { alignOf: 16, sizeOf: 12, requireF16: false },
+ 'vec3<u32>': { alignOf: 16, sizeOf: 12, requireF16: false },
+
+ 'vec4<f32>': { alignOf: 16, sizeOf: 16, requireF16: false },
+ 'vec4<i32>': { alignOf: 16, sizeOf: 16, requireF16: false },
+ 'vec4<u32>': { alignOf: 16, sizeOf: 16, requireF16: false },
+
+ 'mat2x2<f32>': { alignOf: 8, sizeOf: 16, requireF16: false },
+ 'mat3x2<f32>': { alignOf: 8, sizeOf: 24, requireF16: false },
+ 'mat4x2<f32>': { alignOf: 8, sizeOf: 32, requireF16: false },
+ 'mat2x3<f32>': { alignOf: 16, sizeOf: 32, requireF16: false },
+ 'mat3x3<f32>': { alignOf: 16, sizeOf: 48, requireF16: false },
+ 'mat4x3<f32>': { alignOf: 16, sizeOf: 64, requireF16: false },
+ 'mat2x4<f32>': { alignOf: 16, sizeOf: 32, requireF16: false },
+ 'mat3x4<f32>': { alignOf: 16, sizeOf: 48, requireF16: false },
+ 'mat4x4<f32>': { alignOf: 16, sizeOf: 64, requireF16: false },
+
+ S1: { alignOf: 16, sizeOf: 48, requireF16: false },
+ S2: { alignOf: 4, sizeOf: 16 * 7, requireF16: false },
+ S3: { alignOf: 16, sizeOf: 32, requireF16: false }
+};
+
+const wgslTypes = { ...wgslF16Types, ...wgslBaseTypes };
+
+const kWGSLTypes = keysOf(wgslTypes);
+
+function getModuleForWorkgroupStorageSize(device, wgslType, size) {
+ assert(size % kSmallestWorkgroupVarSize === 0);
+ const { sizeOf, alignOf, requireF16 } = wgslTypes[wgslType];
+ const unitSize = align(sizeOf, alignOf);
+ const units = Math.floor(size / unitSize);
+ const extra = (size - units * unitSize) / kSmallestWorkgroupVarSize;
+
+ const code =
+ (requireF16 ? 'enable f16;\n' : '') +
+ `
+ struct S1 {
+ a: f32,
+ b: vec4f,
+ c: u32,
+ };
+ struct S2 {
+ a: array<vec3f, 7>,
+ };
+ struct S3 {
+ a: vec3f,
+ b: vec2f,
+ };
+ var<workgroup> d0: array<${wgslType}, ${units}>;
+ ${extra ? `var<workgroup> d1: array<f32, ${extra}>;` : ''}
+ @compute @workgroup_size(1) fn main() {
+ _ = d0;
+ ${extra ? '_ = d1;' : ''}
+ }
+ `;
+ return { module: device.createShaderModule({ code }), code };
+}
+
+function getDeviceLimitToRequest(
+limitValueTest,
+defaultLimit,
+maximumLimit)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'underDefault':
+ return defaultLimit - kSmallestWorkgroupVarSize;
+ case 'betweenDefaultAndMaximum':
+ return roundDown(Math.floor((defaultLimit + maximumLimit) / 2), kSmallestWorkgroupVarSize);
+ case 'atMaximum':
+ return maximumLimit;
+ case 'overMaximum':
+ return maximumLimit + kSmallestWorkgroupVarSize;
+ }
+}
+
+function getTestValue(testValueName, requestedLimit) {
+ switch (testValueName) {
+ case 'atLimit':
+ return requestedLimit;
+ case 'overLimit':
+ return requestedLimit + kSmallestWorkgroupVarSize;
+ }
+}
+
+function getDeviceLimitToRequestAndValueToTest(
+limitValueTest,
+testValueName,
+defaultLimit,
+maximumLimit)
+{
+ const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
+ const testValue = getTestValue(testValueName, requestedLimit);
+ return {
+ requestedLimit,
+ testValue
+ };
+}
+
+g.test('createComputePipeline,at_over').
+desc(`Test using createComputePipeline(Async) at and over ${limit} limit`).
+params(
+ kMaximumLimitBaseParams.combine('async', [false, true]).combine('wgslType', kWGSLTypes)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, wgslType } = t.params;
+ const { defaultLimit, adapterLimit: maximumLimit } = t;
+
+ const hasF16 = t.adapter.features.has('shader-f16');
+ if (!hasF16 && wgslType in wgslF16Types) {
+ return;
+ }
+
+ const features = hasF16 ? ['shader-f16'] : [];
+
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ maximumLimit
+ );
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const { module, code } = getModuleForWorkgroupStorageSize(device, wgslType, testValue);
+
+ await t.testCreatePipeline(
+ 'createComputePipeline',
+ async,
+ module,
+ shouldError,
+ `size: ${testValue}, limit: ${actualLimit}\n${code}`
+ );
+ },
+ {},
+ features
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension.spec.js
new file mode 100644
index 0000000000..6c43c8da1c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupsPerDimension.spec.js
@@ -0,0 +1,97 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxComputeWorkgroupsPerDimension';
+export const { g, description } = makeLimitTestGroup(limit);
+
+const kCreateComputePipelineTypes = [
+'createComputePipeline',
+'createComputePipelineAsync'];
+
+
+
+async function createComputePipeline(
+device,
+descriptor,
+pipelineType)
+{
+ switch (pipelineType) {
+ case 'createComputePipeline':
+ return device.createComputePipeline(descriptor);
+ case 'createComputePipelineAsync':
+ return await device.createComputePipelineAsync(descriptor);
+ }
+}
+
+// Note: dispatchWorkgroupsIndirect is not tested because it's not a validation error if that exceeds the limits
+g.test('dispatchWorkgroups,at_over').
+desc(`Test using dispatchWorkgroups at and over ${limit} limit`).
+params(
+ kMaximumLimitBaseParams.
+ combine('pipelineType', kCreateComputePipelineTypes).
+ combine('axis', [0, 1, 2])
+).
+fn(async (t) => {
+ const { limitTest, testValueName, pipelineType, axis } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const counts = [1, 1, 1];
+ counts[axis] = testValue;
+
+ const buffer = device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ const module = device.createShaderModule({
+ code: `
+ @compute @workgroup_size(1) fn main() {
+ }
+ `
+ });
+
+ const pipeline = await createComputePipeline(
+ device,
+ {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main'
+ }
+ },
+ pipelineType
+ );
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.dispatchWorkgroups(counts[0], counts[1], counts[2]);
+ pass.end();
+
+ await t.expectValidationError(() => {
+ encoder.finish();
+ }, shouldError);
+
+ buffer.destroy();
+ }
+ );
+});
+
+g.test('validate').
+desc(
+ `Test that ${limit} <= maxComputeWorkgroupSizeX x maxComputeWorkgroupSizeY x maxComputeWorkgroupSizeZ`
+).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ const defaultMaxComputeWorkgroupSizeProduct =
+ t.getDefaultLimit('maxComputeWorkgroupSizeX') *
+ t.getDefaultLimit('maxComputeWorkgroupSizeY') *
+ t.getDefaultLimit('maxComputeWorkgroupSizeZ');
+ const maxComputeWorkgroupSizeProduct =
+ adapter.limits.maxComputeWorkgroupSizeX *
+ adapter.limits.maxComputeWorkgroupSizeY *
+ adapter.limits.maxComputeWorkgroupSizeZ;
+ t.expect(defaultLimit <= defaultMaxComputeWorkgroupSizeProduct);
+ t.expect(adapterLimit <= maxComputeWorkgroupSizeProduct);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout.spec.js
new file mode 100644
index 0000000000..ffd02e5905
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicStorageBuffersPerPipelineLayout.spec.js
@@ -0,0 +1,39 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../../../../common/util/util.js';import { GPUConst } from '../../../../constants.js';
+import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
+
+const limit = 'maxDynamicStorageBuffersPerPipelineLayout';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBindGroupLayout,at_over').
+desc(`Test using createBindGroupLayout at and over ${limit} limit`).
+params(
+ kMaximumLimitBaseParams.combine('visibility', [
+ GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.COMPUTE,
+ GPUConst.ShaderStage.COMPUTE | GPUConst.ShaderStage.FRAGMENT]
+ )
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ shouldError ||= testValue > t.device.limits.maxStorageBuffersPerShaderStage;
+ await t.expectValidationError(() => {
+ device.createBindGroupLayout({
+ entries: range(testValue, (i) => ({
+ binding: i,
+ visibility,
+ buffer: {
+ type: 'storage',
+ hasDynamicOffset: true
+ }
+ }))
+ });
+ }, shouldError);
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout.spec.js
new file mode 100644
index 0000000000..dba8f54747
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxDynamicUniformBuffersPerPipelineLayout.spec.js
@@ -0,0 +1,42 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../../../../common/util/util.js';import { GPUConst } from '../../../../constants.js';
+import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
+
+const limit = 'maxDynamicUniformBuffersPerPipelineLayout';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBindGroupLayout,at_over').
+desc(`Test using createBindGroupLayout at and over ${limit} limit`).
+params(
+ kMaximumLimitBaseParams.combine('visibility', [
+ GPUConst.ShaderStage.VERTEX,
+ GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.COMPUTE,
+ GPUConst.ShaderStage.COMPUTE | GPUConst.ShaderStage.VERTEX,
+ GPUConst.ShaderStage.COMPUTE | GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.COMPUTE | GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT]
+ )
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ shouldError ||= testValue > t.device.limits.maxUniformBuffersPerShaderStage;
+ await t.expectValidationError(() => {
+ device.createBindGroupLayout({
+ entries: range(testValue, (i) => ({
+ binding: i,
+ visibility,
+ buffer: {
+ hasDynamicOffset: true
+ }
+ }))
+ });
+ }, shouldError);
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.js
new file mode 100644
index 0000000000..472630a7bf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.js
@@ -0,0 +1,151 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../../../../common/util/util.js';import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
+
+function getTypeForNumComponents(numComponents) {
+ return numComponents > 1 ? `vec${numComponents}f` : 'f32';
+}
+
+function getPipelineDescriptor(
+device,
+testValue,
+pointList,
+frontFacing,
+sampleIndex,
+sampleMaskIn,
+sampleMaskOut)
+{
+ const maxVertexShaderOutputComponents = testValue - (pointList ? 1 : 0);
+ const maxFragmentShaderInputComponents =
+ testValue - (frontFacing ? 1 : 0) - (sampleIndex ? 1 : 0) - (sampleMaskIn ? 1 : 0);
+
+ const maxInterStageVariables = device.limits.maxInterStageShaderVariables;
+ const numComponents = Math.min(maxVertexShaderOutputComponents, maxFragmentShaderInputComponents);
+
+ const num4ComponentVaryings = Math.floor(numComponents / 4);
+ const lastVaryingNumComponents = numComponents % 4;
+
+ const varyings = `
+ ${range(num4ComponentVaryings, (i) => `@location(${i}) v4_${i}: vec4f,`).join('\n')}
+ ${
+ lastVaryingNumComponents > 0 ?
+ `@location(${num4ComponentVaryings}) vx: ${getTypeForNumComponents(
+ lastVaryingNumComponents
+ )},` :
+ ``
+ }
+ `;
+
+ const code = `
+ // test value : ${testValue}
+ // maxInterStageShaderComponents : ${device.limits.maxInterStageShaderComponents}
+ // num components in vertex shader : ${numComponents}${pointList ? ' + point-list' : ''}
+ // num components in fragment shader : ${numComponents}${frontFacing ? ' + front-facing' : ''}${
+ sampleIndex ? ' + sample_index' : ''
+ }${sampleMaskIn ? ' + sample_mask' : ''}
+ // maxVertexShaderOutputComponents : ${maxVertexShaderOutputComponents}
+ // maxFragmentShaderInputComponents : ${maxFragmentShaderInputComponents}
+ // maxInterStageVariables: : ${maxInterStageVariables}
+ // num used inter stage variables : ${Math.ceil(numComponents / 4)}
+
+ struct VSOut {
+ @builtin(position) p: vec4f,
+ ${varyings}
+ }
+ struct FSIn {
+ ${frontFacing ? '@builtin(front_facing) frontFacing: bool,' : ''}
+ ${sampleIndex ? '@builtin(sample_index) sampleIndex: u32,' : ''}
+ ${sampleMaskIn ? '@builtin(sample_mask) sampleMask: u32,' : ''}
+ ${varyings}
+ }
+ struct FSOut {
+ @location(0) color: vec4f,
+ ${sampleMaskOut ? '@builtin(sample_mask) sampleMask: u32,' : ''}
+ }
+ @vertex fn vs() -> VSOut {
+ var o: VSOut;
+ o.p = vec4f(0);
+ return o;
+ }
+ @fragment fn fs(i: FSIn) -> FSOut {
+ var o: FSOut;
+ o.color = vec4f(0);
+ return o;
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ const pipelineDescriptor = {
+ layout: 'auto',
+ primitive: {
+ topology: pointList ? 'point-list' : 'triangle-list'
+ },
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [
+ {
+ format: 'rgba8unorm'
+ }]
+
+ }
+ };
+ return { pipelineDescriptor, code };
+}
+
+const limit = 'maxInterStageShaderComponents';
+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('pointList', [false, true]).
+ combine('frontFacing', [false, true]).
+ combine('sampleIndex', [false, true]).
+ combine('sampleMaskIn', [false, true]).
+ combine('sampleMaskOut', [false, true])
+).
+beforeAllSubcases((t) => {
+ if (t.isCompatibility && (t.params.sampleMaskIn || t.params.sampleMaskOut)) {
+ t.skip('sample_mask not supported in compatibility mode');
+ }
+}).
+fn(async (t) => {
+ const {
+ limitTest,
+ testValueName,
+ async,
+ pointList,
+ frontFacing,
+ sampleIndex,
+ sampleMaskIn,
+ sampleMaskOut
+ } = t.params;
+ // Request the largest value of maxInterStageShaderVariables to allow the test using as many
+ // inter-stage shader components as possible without being limited by
+ // maxInterStageShaderVariables.
+ const extraLimits = { maxInterStageShaderVariables: 'adapterLimit' };
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const { pipelineDescriptor, code } = getPipelineDescriptor(
+ device,
+ testValue,
+ pointList,
+ frontFacing,
+ sampleIndex,
+ sampleMaskIn,
+ sampleMaskOut
+ );
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError, code);
+ },
+ extraLimits
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables.spec.js
new file mode 100644
index 0000000000..3fe1b4108d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxInterStageShaderVariables.spec.js
@@ -0,0 +1,44 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';function getPipelineDescriptor(device, testValue) {
+ const code = `
+ struct VSOut {
+ @builtin(position) p: vec4f,
+ @location(${testValue}) v: f32,
+ }
+ @vertex fn vs() -> VSOut {
+ var o: VSOut;
+ o.p = vec4f(0);
+ o.v = 1.0;
+ return o;
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ return {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ }
+ };
+}
+
+const limit = 'maxInterStageShaderVariables';
+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])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const lastIndex = testValue - 1;
+ const pipelineDescriptor = getPipelineDescriptor(device, lastIndex);
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.js
new file mode 100644
index 0000000000..07d19ad596
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.js
@@ -0,0 +1,144 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range, reorder,
+ kReorderOrderKeys } from
+
+'../../../../../common/util/util.js';
+import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
+
+import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ kBindGroupTests,
+ kBindingCombinations,
+ getPipelineTypeForBindingCombination,
+ getPerStageWGSLForBindingCombination } from
+'./limit_utils.js';
+
+const limit = 'maxSampledTexturesPerShaderStage';
+export const { g, description } = makeLimitTestGroup(limit);
+
+function createBindGroupLayout(
+device,
+visibility,
+order,
+numBindings)
+{
+ return device.createBindGroupLayout({
+ entries: reorder(
+ order,
+ range(numBindings, (i) => ({
+ binding: i,
+ visibility,
+ texture: {}
+ }))
+ )
+ });
+}
+
+g.test('createBindGroupLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createBindGroupLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', kShaderStageCombinationsWithStage).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.expectValidationError(
+ () => createBindGroupLayout(device, visibility, order, testValue),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipelineLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createPipelineLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', kShaderStageCombinationsWithStage).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const kNumGroups = 3;
+ const bindGroupLayouts = range(kNumGroups, (i) => {
+ const minInGroup = Math.floor(testValue / kNumGroups);
+ const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ return createBindGroupLayout(device, visibility, order, numInGroup);
+ });
+ await t.expectValidationError(
+ () => device.createPipelineLayout({ bindGroupLayouts }),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `
+ Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('async', [false, true]).
+ combine('bindingCombination', kBindingCombinations).
+ combine('order', kReorderOrderKeys).
+ combine('bindGroupTest', kBindGroupTests)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, bindingCombination, order, bindGroupTest } = t.params;
+ const pipelineType = getPipelineTypeForBindingCombination(bindingCombination);
+
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const code = getPerStageWGSLForBindingCombination(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ (i, j) => `var u${j}_${i}: texture_2d<f32>`,
+ (i, j) => `_ = textureLoad(u${j}_${i}, vec2u(0), 0);`,
+ testValue
+ );
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(
+ pipelineType,
+ async,
+ module,
+ shouldError,
+ `actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.js
new file mode 100644
index 0000000000..c3bd3428ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.js
@@ -0,0 +1,145 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range, reorder,
+ kReorderOrderKeys } from
+
+'../../../../../common/util/util.js';
+import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
+
+import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ kBindGroupTests,
+ kBindingCombinations,
+ getPipelineTypeForBindingCombination,
+ getPerStageWGSLForBindingCombination } from
+'./limit_utils.js';
+
+const limit = 'maxSamplersPerShaderStage';
+export const { g, description } = makeLimitTestGroup(limit);
+
+function createBindGroupLayout(
+device,
+visibility,
+order,
+numBindings)
+{
+ return device.createBindGroupLayout({
+ entries: reorder(
+ order,
+ range(numBindings, (i) => ({
+ binding: i,
+ visibility,
+ sampler: {}
+ }))
+ )
+ });
+}
+
+g.test('createBindGroupLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createBindGroupLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', kShaderStageCombinationsWithStage).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.expectValidationError(
+ () => createBindGroupLayout(device, visibility, order, testValue),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipelineLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createPipelineLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', kShaderStageCombinationsWithStage).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const kNumGroups = 3;
+ const bindGroupLayouts = range(kNumGroups, (i) => {
+ const minInGroup = Math.floor(testValue / kNumGroups);
+ const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ return createBindGroupLayout(device, visibility, order, numInGroup);
+ });
+ await t.expectValidationError(
+ () => device.createPipelineLayout({ bindGroupLayouts }),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `
+ Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('async', [false, true]).
+ combine('bindingCombination', kBindingCombinations).
+ combine('order', kReorderOrderKeys).
+ combine('bindGroupTest', kBindGroupTests)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, bindingCombination, order, bindGroupTest } = t.params;
+ const pipelineType = getPipelineTypeForBindingCombination(bindingCombination);
+
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const code = getPerStageWGSLForBindingCombination(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ (i, j) => `var u${j}_${i}: sampler`,
+ (i, j) => `_ = textureGather(0, tex, u${j}_${i}, vec2f(0));`,
+ testValue,
+ '@group(3) @binding(1) var tex: texture_2d<f32>;'
+ );
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(
+ pipelineType,
+ async,
+ module,
+ shouldError,
+ `actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize.spec.js
new file mode 100644
index 0000000000..93052a8d72
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBufferBindingSize.spec.js
@@ -0,0 +1,161 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { align, roundDown } from '../../../../util/math.js';import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup } from
+
+
+
+'./limit_utils.js';
+
+const kBufferParts = ['wholeBuffer', 'biggerBufferWithOffset'];
+
+
+function getSizeAndOffsetForBufferPart(device, bufferPart, size) {
+ const align = device.limits.minUniformBufferOffsetAlignment;
+ switch (bufferPart) {
+ case 'wholeBuffer':
+ return { size, offset: 0 };
+ case 'biggerBufferWithOffset':
+ return { size: size + align, offset: align };
+ }
+}
+
+const kStorageBufferRequiredSizeAlignment = 4;
+
+// We also need to update the maxBufferSize limit when testing.
+const kExtraLimits = { maxBufferSize: 'maxLimit' };
+
+function getDeviceLimitToRequest(
+limitValueTest,
+defaultLimit,
+maximumLimit)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'underDefault':
+ return defaultLimit - kStorageBufferRequiredSizeAlignment;
+ case 'betweenDefaultAndMaximum':
+ return Math.floor((defaultLimit + maximumLimit) / 2);
+ case 'atMaximum':
+ return maximumLimit;
+ case 'overMaximum':
+ return maximumLimit + kStorageBufferRequiredSizeAlignment;
+ }
+}
+
+function getTestValue(testValueName, requestedLimit) {
+ switch (testValueName) {
+ case 'atLimit':
+ return roundDown(requestedLimit, kStorageBufferRequiredSizeAlignment);
+ case 'overLimit':
+ // Note: the requestedLimit might not meet alignment requirements.
+ return align(
+ requestedLimit + kStorageBufferRequiredSizeAlignment,
+ kStorageBufferRequiredSizeAlignment
+ );
+ }
+}
+
+function getDeviceLimitToRequestAndValueToTest(
+limitValueTest,
+testValueName,
+defaultLimit,
+maximumLimit)
+{
+ const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
+ return {
+ requestedLimit,
+ testValue: getTestValue(testValueName, requestedLimit)
+ };
+}
+
+const limit = 'maxStorageBufferBindingSize';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBindGroup,at_over').
+desc(`Test using createBindGroup at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('bufferPart', kBufferParts)).
+fn(async (t) => {
+ const { limitTest, testValueName, bufferPart } = t.params;
+ const { defaultLimit, adapterLimit: maximumLimit } = t;
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ maximumLimit
+ );
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, shouldError }) => {
+ const bindGroupLayout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'storage' }
+ }]
+
+ });
+
+ const { size, offset } = getSizeAndOffsetForBufferPart(device, bufferPart, testValue);
+
+ // If the size of the buffer exceeds the related but separate maxBufferSize limit, we can
+ // skip the validation since the allocation will fail with a validation error.
+ if (size > device.limits.maxBufferSize) {
+ return;
+ }
+
+ device.pushErrorScope('out-of-memory');
+ const storageBuffer = t.trackForCleanup(
+ device.createBuffer({
+ usage: GPUBufferUsage.STORAGE,
+ size
+ })
+ );
+ const outOfMemoryError = await device.popErrorScope();
+
+ if (!outOfMemoryError) {
+ await t.expectValidationError(
+ () => {
+ device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: storageBuffer,
+ offset,
+ size: testValue
+ }
+ }]
+
+ });
+ },
+ shouldError,
+ `size: ${size}, offset: ${offset}, testValue: ${testValue}`
+ );
+ }
+ },
+ kExtraLimits
+ );
+});
+
+g.test('validate').
+desc(`Test that ${limit} is a multiple of 4 bytes`).
+fn((t) => {
+ const { defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit % 4 === 0);
+ t.expect(adapterLimit % 4 === 0);
+});
+
+g.test('validate,maxBufferSize').
+desc(`Test that ${limit} <= maxBufferSize`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxBufferSize'));
+ t.expect(adapterLimit <= adapter.limits.maxBufferSize);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.js
new file mode 100644
index 0000000000..1c09dcfcfd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.js
@@ -0,0 +1,174 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range, reorder,
+ kReorderOrderKeys } from
+
+'../../../../../common/util/util.js';
+import { GPUConst } from '../../../../constants.js';
+
+import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ kBindGroupTests,
+ kBindingCombinations,
+ getPipelineTypeForBindingCombination,
+ getPerStageWGSLForBindingCombination } from
+'./limit_utils.js';
+
+const limit = 'maxStorageBuffersPerShaderStage';
+export const { g, description } = makeLimitTestGroup(limit);
+
+function createBindGroupLayout(
+device,
+visibility,
+type,
+order,
+numBindings)
+{
+ return device.createBindGroupLayout({
+ entries: reorder(
+ order,
+ range(numBindings, (i) => ({
+ binding: i,
+ visibility,
+ buffer: { type }
+ }))
+ )
+ });
+}
+
+g.test('createBindGroupLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createBindGroupLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+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('type', ['storage', 'read-only-storage']).
+ combine('order', kReorderOrderKeys)
+).
+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 }) => {
+ await t.expectValidationError(() => {
+ createBindGroupLayout(device, visibility, type, order, testValue);
+ }, shouldError);
+ }
+ );
+});
+
+g.test('createPipelineLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createPipelineLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+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('type', ['storage', 'read-only-storage']).
+ combine('order', kReorderOrderKeys)
+).
+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;
+ const bindGroupLayouts = range(kNumGroups, (i) => {
+ const minInGroup = Math.floor(testValue / kNumGroups);
+ const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ return createBindGroupLayout(device, visibility, type, order, numInGroup);
+ });
+ await t.expectValidationError(
+ () => device.createPipelineLayout({ bindGroupLayouts }),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `
+ Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('async', [false, true]).
+ combine('bindingCombination', kBindingCombinations).
+ combine('order', kReorderOrderKeys).
+ combine('bindGroupTest', kBindGroupTests)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, bindingCombination, order, bindGroupTest } = t.params;
+ const pipelineType = getPipelineTypeForBindingCombination(bindingCombination);
+
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const code = getPerStageWGSLForBindingCombination(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ (i, j) => `var<storage> u${j}_${i}: f32`,
+ (i, j) => `_ = u${j}_${i};`,
+ testValue
+ );
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(
+ pipelineType,
+ async,
+ module,
+ shouldError,
+ `actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.js
new file mode 100644
index 0000000000..5005ae35fe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.js
@@ -0,0 +1,156 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range, reorder,
+
+ kReorderOrderKeys } from
+'../../../../../common/util/util.js';
+import { GPUConst } from '../../../../constants.js';
+
+import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ kBindGroupTests,
+ getPerStageWGSLForBindingCombinationStorageTextures,
+ getPipelineTypeForBindingCombination } from
+
+'./limit_utils.js';
+
+const limit = 'maxStorageTexturesPerShaderStage';
+export const { g, description } = makeLimitTestGroup(limit);
+
+function createBindGroupLayout(
+device,
+visibility,
+order,
+numBindings)
+{
+ return device.createBindGroupLayout({
+ entries: reorder(
+ order,
+ range(numBindings, (i) => ({
+ binding: i,
+ visibility,
+ storageTexture: { format: 'rgba8unorm' }
+ }))
+ )
+ });
+}
+
+g.test('createBindGroupLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createBindGroupLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', [
+ GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.COMPUTE,
+ GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE]
+ ).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.expectValidationError(
+ () => createBindGroupLayout(device, visibility, order, testValue),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipelineLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createPipelineLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', [
+ GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.COMPUTE,
+ GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE]
+ ).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const kNumGroups = 3;
+ const bindGroupLayouts = range(kNumGroups, (i) => {
+ const minInGroup = Math.floor(testValue / kNumGroups);
+ const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ return createBindGroupLayout(device, visibility, order, numInGroup);
+ });
+ await t.expectValidationError(
+ () => device.createPipelineLayout({ bindGroupLayouts }),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `
+ Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('async', [false, true]).
+ combine('bindingCombination', ['fragment', 'compute']).
+ combine('order', kReorderOrderKeys).
+ combine('bindGroupTest', kBindGroupTests)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, bindingCombination, order, bindGroupTest } = t.params;
+ const pipelineType = getPipelineTypeForBindingCombination(bindingCombination);
+
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ if (bindingCombination === 'fragment') {
+ return;
+ }
+
+ const code = getPerStageWGSLForBindingCombinationStorageTextures(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ (i, j) => `var u${j}_${i}: texture_storage_2d<rgba8unorm, write>`,
+ (i, j) => `textureStore(u${j}_${i}, vec2u(0), vec4f(1));`,
+ testValue
+ );
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(
+ pipelineType,
+ async,
+ module,
+ shouldError,
+ `actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers.spec.js
new file mode 100644
index 0000000000..7a9cc1e230
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureArrayLayers.spec.js
@@ -0,0 +1,27 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxTextureArrayLayers';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createTexture,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(() => {
+ const texture = device.createTexture({
+ size: [1, 1, testValue],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+ if (!shouldError) {
+ texture.destroy();
+ }
+ }, shouldError);
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D.spec.js
new file mode 100644
index 0000000000..3d4608dde3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension1D.spec.js
@@ -0,0 +1,34 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxTextureDimension1D';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createTexture,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(() => {
+ const texture = device.createTexture({
+ size: [testValue, 1, 1],
+ format: 'rgba8unorm',
+ dimension: '1d',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ // MAINTENANCE_TODO: Remove this 'if' once the bug in chrome is fixed
+ // This 'if' is only here because of a bug in Chrome
+ // that generates an error calling destroy on an invalid texture.
+ // This doesn't affect the test but the 'if' should be removed
+ // once the Chrome bug is fixed.
+ if (!shouldError) {
+ texture.destroy();
+ }
+ }, shouldError);
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D.spec.js
new file mode 100644
index 0000000000..5f3b3a4def
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension2D.spec.js
@@ -0,0 +1,133 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { getGPU } from '../../../../../common/util/navigator_gpu.js';import { kAllCanvasTypes, createCanvas } from '../../../../util/create_elements.js';
+import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
+
+const limit = 'maxTextureDimension2D';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createTexture,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, shouldError, testValue, actualLimit }) => {
+ for (let dimensionIndex = 0; dimensionIndex < 2; ++dimensionIndex) {
+ const size = [1, 1, 1];
+ size[dimensionIndex] = testValue;
+
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(
+ () => {
+ const texture = device.createTexture({
+ size,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ // MAINTENANCE_TODO: Remove this 'if' once the bug in chrome is fixed
+ // This 'if' is only here because of a bug in Chrome
+ // that generates an error calling destroy on an invalid texture.
+ // This doesn't affect the test but the 'if' should be removed
+ // once the Chrome bug is fixed.
+ if (!shouldError) {
+ texture.destroy();
+ }
+ },
+ shouldError,
+ `size: ${size}, actualLimit: ${actualLimit}`
+ );
+ }
+ }
+ );
+});
+
+g.test('configure,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('canvasType', kAllCanvasTypes)).
+fn(async (t) => {
+ const { limitTest, testValueName, canvasType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, shouldError, testValue, actualLimit }) => {
+ for (let dimensionIndex = 0; dimensionIndex < 2; ++dimensionIndex) {
+ const size = [1, 1];
+ size[dimensionIndex] = testValue;
+
+ // This should not fail, even if the size is too large but it might fail
+ // if we're in a worker and HTMLCanvasElement does not exist.
+ const canvas = createCanvas(t, canvasType, size[0], size[1]);
+ if (canvas) {
+ const context = canvas.getContext('webgpu');
+ t.expect(!!context, 'should not fail to create context even if size is too large');
+
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(
+ () => {
+ context.configure({
+ device,
+ format: getGPU(t.rec).getPreferredCanvasFormat()
+ });
+ },
+ shouldError,
+ `size: ${size}, actualLimit: ${actualLimit}`
+ );
+ }
+ }
+ }
+ );
+});
+
+g.test('getCurrentTexture,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('canvasType', kAllCanvasTypes)).
+fn(async (t) => {
+ const { limitTest, testValueName, canvasType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, shouldError, testValue, actualLimit }) => {
+ for (let dimensionIndex = 0; dimensionIndex < 2; ++dimensionIndex) {
+ const size = [1, 1];
+ size[dimensionIndex] = testValue;
+
+ // Start with a small size so configure will succeed.
+ // This should not fail, even if the size is too large but it might fail
+ // if we're in a worker and HTMLCanvasElement does not exist.
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ if (canvas) {
+ const context = canvas.getContext('webgpu');
+ t.expect(!!context, 'should not fail to create context even if size is too large');
+
+ context.configure({
+ device,
+ format: getGPU(t.rec).getPreferredCanvasFormat()
+ });
+
+ if (canvas) {
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(
+ () => {
+ canvas.width = size[0];
+ canvas.height = size[1];
+ const texture = context.getCurrentTexture();
+
+ // MAINTENANCE_TODO: Remove this 'if' once the bug in chrome is fixed
+ // This 'if' is only here because of a bug in Chrome
+ // that generates an error calling destroy on an invalid texture.
+ // This doesn't affect the test but the 'if' should be removed
+ // once the Chrome bug is fixed.
+ if (!shouldError) {
+ texture.destroy();
+ }
+ },
+ shouldError,
+ `size: ${size}, actualLimit: ${actualLimit}`
+ );
+ }
+ }
+ }
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D.spec.js
new file mode 100644
index 0000000000..74108cc85e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxTextureDimension3D.spec.js
@@ -0,0 +1,39 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const limit = 'maxTextureDimension3D';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createTexture,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, shouldError, testValue }) => {
+ for (let dimensionIndex = 0; dimensionIndex < 3; ++dimensionIndex) {
+ const size = [2, 2, 2];
+ size[dimensionIndex] = testValue;
+
+ await t.testForValidationErrorWithPossibleOutOfMemoryError(() => {
+ const texture = device.createTexture({
+ size,
+ format: 'rgba8unorm',
+ dimension: '3d',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ // MAINTENANCE_TODO: Remove this 'if' once the bug in chrome is fixed
+ // This 'if' is only here because of a bug in Chrome
+ // that generates an error calling destroy on an invalid texture.
+ // This doesn't affect the test but the 'if' should be removed
+ // once the Chrome bug is fixed.
+ if (!shouldError) {
+ texture.destroy();
+ }
+ }, shouldError);
+ }
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize.spec.js
new file mode 100644
index 0000000000..40b22ed60b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBufferBindingSize.spec.js
@@ -0,0 +1,90 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';const kBufferParts = ['wholeBuffer', 'biggerBufferWithOffset'];
+
+
+function getSizeAndOffsetForBufferPart(device, bufferPart, size) {
+ const align = device.limits.minUniformBufferOffsetAlignment;
+ switch (bufferPart) {
+ case 'wholeBuffer':
+ return { offset: 0, size };
+ case 'biggerBufferWithOffset':
+ return { size: size + align, offset: align };
+ }
+}
+
+const limit = 'maxUniformBufferBindingSize';
+export const { g, description } = makeLimitTestGroup(limit);
+
+// We also need to update the maxBufferSize limit when testing.
+const kExtraLimits = { maxBufferSize: 'maxLimit' };
+
+g.test('createBindGroup,at_over').
+desc(`Test using at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('bufferPart', kBufferParts)).
+fn(async (t) => {
+ const { limitTest, testValueName, bufferPart } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const bindGroupLayout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+
+ const { size, offset } = getSizeAndOffsetForBufferPart(device, bufferPart, testValue);
+
+ // If the size of the buffer exceeds the related but separate maxBufferSize limit, we can
+ // skip the validation since the allocation will fail with a validation error.
+ if (size > device.limits.maxBufferSize) {
+ return;
+ }
+
+ device.pushErrorScope('out-of-memory');
+ const uniformBuffer = t.trackForCleanup(
+ device.createBuffer({
+ usage: GPUBufferUsage.UNIFORM,
+ size
+ })
+ );
+ const outOfMemoryError = await device.popErrorScope();
+
+ if (!outOfMemoryError) {
+ await t.expectValidationError(
+ () => {
+ device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: uniformBuffer,
+ offset,
+ size: testValue
+ }
+ }]
+
+ });
+ },
+ shouldError,
+ `size: ${size}, offset: ${offset}, testValue: ${testValue}`
+ );
+ }
+ },
+ kExtraLimits
+ );
+});
+
+g.test('validate,maxBufferSize').
+desc(`Test that ${limit} <= maxBufferSize`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxBufferSize'));
+ t.expect(adapterLimit <= adapter.limits.maxBufferSize);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.js
new file mode 100644
index 0000000000..191493046a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.js
@@ -0,0 +1,144 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range, reorder,
+ kReorderOrderKeys } from
+
+'../../../../../common/util/util.js';
+import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
+
+import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ kBindGroupTests,
+ kBindingCombinations,
+ getPipelineTypeForBindingCombination,
+ getPerStageWGSLForBindingCombination } from
+'./limit_utils.js';
+
+const limit = 'maxUniformBuffersPerShaderStage';
+export const { g, description } = makeLimitTestGroup(limit);
+
+function createBindGroupLayout(
+device,
+visibility,
+order,
+numBindings)
+{
+ return device.createBindGroupLayout({
+ entries: reorder(
+ order,
+ range(numBindings, (i) => ({
+ binding: i,
+ visibility,
+ buffer: {}
+ }))
+ )
+ });
+}
+
+g.test('createBindGroupLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createBindGroupLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', kShaderStageCombinationsWithStage).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ await t.expectValidationError(
+ () => createBindGroupLayout(device, visibility, order, testValue),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipelineLayout,at_over').
+desc(
+ `
+ Test using at and over ${limit} limit in createPipelineLayout
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('visibility', kShaderStageCombinationsWithStage).
+ combine('order', kReorderOrderKeys)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, visibility, order } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const kNumGroups = 3;
+ const bindGroupLayouts = range(kNumGroups, (i) => {
+ const minInGroup = Math.floor(testValue / kNumGroups);
+ const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ return createBindGroupLayout(device, visibility, order, numInGroup);
+ });
+ await t.expectValidationError(
+ () => device.createPipelineLayout({ bindGroupLayouts }),
+ shouldError
+ );
+ }
+ );
+});
+
+g.test('createPipeline,at_over').
+desc(
+ `
+ Test using createRenderPipeline(Async) and createComputePipeline(Async) at and over ${limit} limit
+
+ Note: We also test order to make sure the implementation isn't just looking
+ at just the last entry.
+ `
+).
+params(
+ kMaximumLimitBaseParams.
+ combine('async', [false, true]).
+ combine('bindingCombination', kBindingCombinations).
+ combine('order', kReorderOrderKeys).
+ combine('bindGroupTest', kBindGroupTests)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, bindingCombination, order, bindGroupTest } = t.params;
+ const pipelineType = getPipelineTypeForBindingCombination(bindingCombination);
+
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, actualLimit, shouldError }) => {
+ const code = getPerStageWGSLForBindingCombination(
+ bindingCombination,
+ order,
+ bindGroupTest,
+ (i, j) => `var<uniform> u${j}_${i}: f32`,
+ (i, j) => `_ = u${j}_${i};`,
+ testValue
+ );
+ const module = device.createShaderModule({ code });
+
+ await t.testCreatePipeline(
+ pipelineType,
+ async,
+ module,
+ shouldError,
+ `actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
+ );
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexAttributes.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexAttributes.spec.js
new file mode 100644
index 0000000000..8908742821
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexAttributes.spec.js
@@ -0,0 +1,43 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';function getPipelineDescriptor(device, lastIndex) {
+ const code = `
+ @vertex fn vs(@location(${lastIndex}) v: vec4f) -> @builtin(position) vec4f {
+ return v;
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ return {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ buffers: [
+ {
+ arrayStride: 32,
+ attributes: [{ shaderLocation: lastIndex, offset: 0, format: 'float32x4' }]
+ }]
+
+ }
+ };
+}
+
+const limit = 'maxVertexAttributes';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createRenderPipeline,at_over').
+desc(`Test using createRenderPipeline(Async) at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('async', [false, true])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const lastIndex = testValue - 1;
+ const pipelineDescriptor = getPipelineDescriptor(device, lastIndex);
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride.spec.js
new file mode 100644
index 0000000000..2879e4022d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBufferArrayStride.spec.js
@@ -0,0 +1,121 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { roundDown } from '../../../../util/math.js';import {
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup } from
+
+
+'./limit_utils.js';
+
+function getPipelineDescriptor(device, testValue) {
+ const code = `
+ @vertex fn vs(@location(0) v: f32) -> @builtin(position) vec4f {
+ return vec4f(v);
+ }
+ `;
+ const module = device.createShaderModule({ code });
+ return {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ buffers: [
+ {
+ arrayStride: testValue,
+ attributes: [
+ {
+ shaderLocation: 0,
+ offset: 0,
+ format: 'float32'
+ }]
+
+ }]
+
+ }
+ };
+}
+
+const kMinAttributeStride = 4;
+
+function getDeviceLimitToRequest(
+limitValueTest,
+defaultLimit,
+maximumLimit)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'underDefault':
+ return defaultLimit - kMinAttributeStride;
+ case 'betweenDefaultAndMaximum':
+ return Math.min(
+ defaultLimit,
+ roundDown(Math.floor((defaultLimit + maximumLimit) / 2), kMinAttributeStride)
+ );
+ case 'atMaximum':
+ return maximumLimit;
+ case 'overMaximum':
+ return maximumLimit + kMinAttributeStride;
+ }
+}
+
+function getTestValue(testValueName, requestedLimit) {
+ switch (testValueName) {
+ case 'atLimit':
+ return requestedLimit;
+ case 'overLimit':
+ return requestedLimit + kMinAttributeStride;
+ }
+}
+
+function getDeviceLimitToRequestAndValueToTest(
+limitValueTest,
+testValueName,
+defaultLimit,
+maximumLimit)
+{
+ const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
+ return {
+ requestedLimit,
+ testValue: getTestValue(testValueName, requestedLimit)
+ };
+}
+
+/*
+Note: We need to request +4 (vs the default +1) because otherwise we may trigger the wrong validation
+of the arrayStride not being a multiple of 4
+*/
+const limit = 'maxVertexBufferArrayStride';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createRenderPipeline,at_over').
+desc(`Test using createRenderPipeline(Async) at and over ${limit} limit`).
+params(kMaximumLimitBaseParams.combine('async', [false, true])).
+fn(async (t) => {
+ const { limitTest, testValueName, async } = t.params;
+ const { defaultLimit, adapterLimit: maximumLimit } = t;
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ maximumLimit
+ );
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, shouldError }) => {
+ const pipelineDescriptor = getPipelineDescriptor(device, testValue);
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ }
+ );
+});
+
+g.test('validate').
+desc(`Test that ${limit} is a multiple of 4 bytes`).
+fn((t) => {
+ const { defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit % 4 === 0);
+ t.expect(adapterLimit % 4 === 0);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.js
new file mode 100644
index 0000000000..860c40687e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.js
@@ -0,0 +1,100 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../../../../common/util/util.js';import { kRenderEncoderTypes, kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
+
+const kPipelineTypes = ['withoutLocations', 'withLocations'];
+
+
+function getPipelineDescriptor(
+device,
+pipelineType,
+testValue)
+{
+ 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' }]
+ }))
+ }
+ };
+}
+
+const limit = 'maxVertexBuffers';
+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)
+).
+fn(async (t) => {
+ const { limitTest, testValueName, async, pipelineType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError }) => {
+ const pipelineDescriptor = getPipelineDescriptor(device, pipelineType, testValue);
+
+ await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ }
+ );
+});
+
+g.test('setVertexBuffer,at_over').
+desc(`Test using at and over ${limit} limit in setVertexBuffer`).
+params(kMaximumLimitBaseParams.combine('encoderType', kRenderEncoderTypes)).
+fn(async (t) => {
+ const { limitTest, testValueName, encoderType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const lastIndex = testValue - 1;
+
+ const buffer = device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.VERTEX
+ });
+
+ await t.testGPURenderCommandsMixin(
+ encoderType,
+ ({ mixin }) => {
+ mixin.setVertexBuffer(lastIndex, buffer);
+ },
+ shouldError,
+ `lastIndex: ${lastIndex}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
+ );
+
+ buffer.destroy();
+ }
+ );
+});
+
+g.test('validate,maxBindGroupsPlusVertexBuffers').
+desc(`Test that ${limit} <= maxBindGroupsPlusVertexBuffers`).
+fn((t) => {
+ const { adapter, defaultLimit, adapterLimit } = t;
+ t.expect(defaultLimit <= t.getDefaultLimit('maxBindGroupsPlusVertexBuffers'));
+ t.expect(adapterLimit <= adapter.limits.maxBindGroupsPlusVertexBuffers);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment.spec.js
new file mode 100644
index 0000000000..5735818916
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minStorageBufferOffsetAlignment.spec.js
@@ -0,0 +1,183 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { GPUConst } from '../../../../constants.js';import { isPowerOfTwo } from '../../../../util/math.js';
+import {
+ kMinimumLimitBaseParams,
+ makeLimitTestGroup } from
+
+
+'./limit_utils.js';
+
+function getDeviceLimitToRequest(
+limitValueTest,
+defaultLimit,
+minimumLimit)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'overDefault':
+ return 2 ** (Math.log2(defaultLimit) + 1);
+ case 'betweenDefaultAndMinimum':
+ return 2 ** ((Math.log2(defaultLimit) + Math.log2(minimumLimit)) / 2 | 0);
+ case 'atMinimum':
+ return minimumLimit;
+ case 'underMinimum':
+ return 2 ** (Math.log2(minimumLimit) - 1);
+ }
+}
+
+function getTestValue(testValueName, requestedLimit) {
+ switch (testValueName) {
+ case 'atLimit':
+ return requestedLimit;
+ case 'underLimit':
+ return 2 ** (Math.log2(requestedLimit) - 1);
+ }
+}
+
+function getDeviceLimitToRequestAndValueToTest(
+limitValueTest,
+testValueName,
+defaultLimit,
+maximumLimit)
+{
+ const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
+ return {
+ requestedLimit,
+ testValue: getTestValue(testValueName, requestedLimit)
+ };
+}
+
+const limit = 'minStorageBufferOffsetAlignment';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBindGroup,at_over').
+desc(`Test using createBindGroup at and over ${limit} limit`).
+params(kMinimumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ // note: LimitTest.maximum is the adapter.limits[limit] value
+ const { defaultLimit, adapterLimit: minimumLimit } = t;
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ minimumLimit
+ );
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, shouldError }) => {
+ const storageBuffer = t.trackForCleanup(
+ device.createBuffer({
+ size: testValue * 2,
+ usage: GPUBufferUsage.STORAGE
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'storage' }
+ }]
+
+ });
+
+ await t.expectValidationError(() => {
+ device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: storageBuffer,
+ offset: testValue
+ }
+ }]
+
+ });
+ }, shouldError);
+ }
+ );
+});
+
+g.test('setBindGroup,at_over').
+desc(`Test using setBindGroup at and over ${limit} limit`).
+params(kMinimumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ // note: LimitTest.maximum is the adapter.limits[limit] value
+ const { defaultLimit, adapterLimit: minimumLimit } = t;
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ minimumLimit
+ );
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, shouldError }) => {
+ const buffer = device.createBuffer({
+ size: testValue * 2,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUConst.ShaderStage.COMPUTE,
+ buffer: {
+ type: 'storage',
+ hasDynamicOffset: true
+ }
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ size: testValue / 2
+ }
+ }]
+
+ });
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setBindGroup(0, bindGroup, [testValue]);
+ pass.end();
+
+ await t.expectValidationError(() => {
+ encoder.finish();
+ }, shouldError);
+
+ buffer.destroy();
+ }
+ );
+});
+
+g.test('validate,powerOf2').
+desc('Verify that ${limit} is power of 2').
+fn((t) => {
+ t.expect(isPowerOfTwo(t.defaultLimit));
+ t.expect(isPowerOfTwo(t.adapterLimit));
+});
+
+g.test('validate,greaterThanOrEqualTo32').
+desc('Verify that ${limit} is >= 32').
+fn((t) => {
+ t.expect(t.defaultLimit >= 32);
+ t.expect(t.adapterLimit >= 32);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment.spec.js
new file mode 100644
index 0000000000..93d2b3eacf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/capability_checks/limits/minUniformBufferOffsetAlignment.spec.js
@@ -0,0 +1,186 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { GPUConst } from '../../../../constants.js';import { isPowerOfTwo } from '../../../../util/math.js';
+import {
+ kMinimumLimitBaseParams,
+ makeLimitTestGroup } from
+
+
+'./limit_utils.js';
+
+function getDeviceLimitToRequest(
+limitValueTest,
+defaultLimit,
+minimumLimit)
+{
+ switch (limitValueTest) {
+ case 'atDefault':
+ return defaultLimit;
+ case 'overDefault':
+ return 2 ** (Math.log2(defaultLimit) + 1);
+ case 'betweenDefaultAndMinimum':
+ return Math.min(
+ minimumLimit,
+ 2 ** ((Math.log2(defaultLimit) + Math.log2(minimumLimit)) / 2 | 0)
+ );
+ case 'atMinimum':
+ return minimumLimit;
+ case 'underMinimum':
+ return 2 ** (Math.log2(minimumLimit) - 1);
+ }
+}
+
+function getTestValue(testValueName, requestedLimit) {
+ switch (testValueName) {
+ case 'atLimit':
+ return requestedLimit;
+ case 'underLimit':
+ return 2 ** (Math.log2(requestedLimit) - 1);
+ }
+}
+
+function getDeviceLimitToRequestAndValueToTest(
+limitValueTest,
+testValueName,
+defaultLimit,
+maximumLimit)
+{
+ const requestedLimit = getDeviceLimitToRequest(limitValueTest, defaultLimit, maximumLimit);
+ return {
+ requestedLimit,
+ testValue: getTestValue(testValueName, requestedLimit)
+ };
+}
+
+const limit = 'minUniformBufferOffsetAlignment';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createBindGroup,at_over').
+desc(`Test using createBindGroup at and over ${limit} limit`).
+params(kMinimumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ // note: LimitTest.maximum is the adapter.limits[limit] value
+ const { defaultLimit, adapterLimit: minimumLimit } = t;
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ minimumLimit
+ );
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, shouldError }) => {
+ const buffer = t.trackForCleanup(
+ device.createBuffer({
+ size: testValue * 2,
+ usage: GPUBufferUsage.UNIFORM
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: {}
+ }]
+
+ });
+
+ await t.expectValidationError(() => {
+ device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ offset: testValue
+ }
+ }]
+
+ });
+ }, shouldError);
+ }
+ );
+});
+
+g.test('setBindGroup,at_over').
+desc(`Test using setBindGroup at and over ${limit} limit`).
+params(kMinimumLimitBaseParams).
+fn(async (t) => {
+ const { limitTest, testValueName } = t.params;
+ // note: LimitTest.maximum is the adapter.limits[limit] value
+ const { defaultLimit, adapterLimit: minimumLimit } = t;
+ const { requestedLimit, testValue } = getDeviceLimitToRequestAndValueToTest(
+ limitTest,
+ testValueName,
+ defaultLimit,
+ minimumLimit
+ );
+
+ await t.testDeviceWithSpecificLimits(
+ requestedLimit,
+ testValue,
+ async ({ device, testValue, shouldError }) => {
+ const buffer = device.createBuffer({
+ size: testValue * 2,
+ usage: GPUBufferUsage.UNIFORM
+ });
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUConst.ShaderStage.COMPUTE,
+ buffer: {
+ type: 'uniform',
+ hasDynamicOffset: true
+ }
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ size: testValue / 2
+ }
+ }]
+
+ });
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setBindGroup(0, bindGroup, [testValue]);
+ pass.end();
+
+ await t.expectValidationError(() => {
+ encoder.finish();
+ }, shouldError);
+
+ buffer.destroy();
+ }
+ );
+});
+
+g.test('validate,powerOf2').
+desc('Verify that ${limit} is power of 2').
+fn((t) => {
+ t.expect(isPowerOfTwo(t.defaultLimit));
+ t.expect(isPowerOfTwo(t.adapterLimit));
+});
+
+g.test('validate,greaterThanOrEqualTo32').
+desc('Verify that ${limit} is >= 32').
+fn((t) => {
+ t.expect(t.defaultLimit >= 32);
+ t.expect(t.adapterLimit >= 32);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/compute_pipeline.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/compute_pipeline.spec.js
new file mode 100644
index 0000000000..3d2c5a7641
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/compute_pipeline.spec.js
@@ -0,0 +1,692 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+createComputePipeline and createComputePipelineAsync validation tests.
+
+Note: entry point matching tests are in shader_module/entry_point.spec.ts
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { kValue } from '../../util/constants.js';
+import { getShaderWithEntryPoint } from '../../util/shader.js';
+
+import { ValidationTest } from './validation_test.js';
+
+class F extends ValidationTest {
+ getShaderModule(
+ shaderStage = 'compute',
+ entryPoint = 'main')
+ {
+ return this.device.createShaderModule({
+ code: getShaderWithEntryPoint(shaderStage, entryPoint)
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('basic').
+desc(
+ `
+Control case for createComputePipeline and createComputePipelineAsync.
+Call the API with valid compute shader and matching valid entryPoint, making sure that the test function working well.
+`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn((t) => {
+ const { isAsync } = t.params;
+ t.doCreateComputePipelineTest(isAsync, true, {
+ layout: 'auto',
+ compute: { module: t.getShaderModule('compute', 'main'), entryPoint: 'main' }
+ });
+});
+
+g.test('shader_module,invalid').
+desc(
+ `
+Tests calling createComputePipeline(Async) with a invalid compute shader, and check that the APIs catch this error.
+`
+).
+params((u) => u.combine('isAsync', [true, false])).
+fn((t) => {
+ const { isAsync } = t.params;
+ t.doCreateComputePipelineTest(isAsync, false, {
+ layout: 'auto',
+ compute: {
+ module: t.createInvalidShaderModule(),
+ entryPoint: 'main'
+ }
+ });
+});
+
+g.test('shader_module,compute').
+desc(
+ `
+Tests calling createComputePipeline(Async) with valid but different stage shader and matching entryPoint,
+and check that the APIs only accept compute shader.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combine('shaderModuleStage', ['compute', 'vertex', 'fragment'])
+).
+fn((t) => {
+ const { isAsync, shaderModuleStage } = t.params;
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.getShaderModule(shaderModuleStage, 'main'),
+ entryPoint: 'main'
+ }
+ };
+ t.doCreateComputePipelineTest(isAsync, shaderModuleStage === 'compute', descriptor);
+});
+
+g.test('shader_module,device_mismatch').
+desc(
+ 'Tests createComputePipeline(Async) cannot be called with a shader module created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('isAsync', [true, false]).combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { isAsync, mismatched } = t.params;
+
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const module = sourceDevice.createShaderModule({
+ code: '@compute @workgroup_size(1) fn main() {}'
+ });
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'main'
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, !mismatched, descriptor);
+});
+
+g.test('pipeline_layout,device_mismatch').
+desc(
+ 'Tests createComputePipeline(Async) cannot be called with a pipeline layout created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('isAsync', [true, false]).combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { isAsync, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const layout = sourceDevice.createPipelineLayout({ bindGroupLayouts: [] });
+
+ const descriptor = {
+ layout,
+ compute: {
+ module: t.getShaderModule('compute', 'main'),
+ entryPoint: 'main'
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, !mismatched, descriptor);
+});
+
+g.test('limits,workgroup_storage_size').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for compute using <= device.limits.maxComputeWorkgroupStorageSize bytes of workgroup storage.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ type: 'vec4<f32>', _typeSize: 16 },
+{ type: 'mat4x4<f32>', _typeSize: 64 }]
+).
+beginSubcases().
+combine('countDeltaFromLimit', [0, 1])
+).
+fn((t) => {
+ const { isAsync, type, _typeSize, countDeltaFromLimit } = t.params;
+ const countAtLimit = Math.floor(t.device.limits.maxComputeWorkgroupStorageSize / _typeSize);
+ const count = countAtLimit + countDeltaFromLimit;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ var<workgroup> data: array<${type}, ${count}>;
+ @compute @workgroup_size(64) fn main () {
+ _ = data;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ };
+ t.doCreateComputePipelineTest(isAsync, count <= countAtLimit, descriptor);
+});
+
+g.test('limits,invocations_per_workgroup').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for compute using <= device.limits.maxComputeInvocationsPerWorkgroup per workgroup.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combine('size', [
+// Assume maxComputeWorkgroupSizeX/Y >= 129, maxComputeWorkgroupSizeZ >= 33
+[128, 1, 2],
+[129, 1, 2],
+[2, 128, 1],
+[2, 129, 1],
+[1, 8, 32],
+[1, 8, 33]]
+)
+).
+fn((t) => {
+ const { isAsync, size } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @compute @workgroup_size(${size.join(',')}) fn main () {
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ };
+
+ t.doCreateComputePipelineTest(
+ isAsync,
+ size[0] * size[1] * size[2] <= t.device.limits.maxComputeInvocationsPerWorkgroup,
+ descriptor
+ );
+});
+
+g.test('limits,invocations_per_workgroup,each_component').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for compute workgroup_size attribute has each component no more than their limits.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combine('size', [
+// Assume maxComputeInvocationsPerWorkgroup >= 256
+[64],
+[256, 1, 1],
+[257, 1, 1],
+[1, 256, 1],
+[1, 257, 1],
+[1, 1, 63],
+[1, 1, 64],
+[1, 1, 65]]
+)
+).
+fn((t) => {
+ const { isAsync, size } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @compute @workgroup_size(${size.join(',')}) fn main () {
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ };
+
+ const workgroupX = size[0];
+ const workgroupY = size[1] ?? 1;
+ const workgroupZ = size[2] ?? 1;
+
+ const _success =
+ workgroupX <= t.device.limits.maxComputeWorkgroupSizeX &&
+ workgroupY <= t.device.limits.maxComputeWorkgroupSizeY &&
+ workgroupZ <= t.device.limits.maxComputeWorkgroupSizeZ;
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('overrides,identifier').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for overridable constants identifiers.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ constants: {}, _success: true },
+{ constants: { c0: 0 }, _success: true },
+{ constants: { c0: 0, c1: 1 }, _success: true },
+{ constants: { 'c0\0': 0 }, _success: false },
+{ constants: { c9: 0 }, _success: false },
+{ constants: { 1: 0 }, _success: true },
+{ constants: { c3: 0 }, _success: false }, // pipeline constant id is specified for c3
+{ constants: { 2: 0 }, _success: false },
+{ constants: { 1000: 0 }, _success: true },
+{ constants: { 9999: 0 }, _success: false },
+{ constants: { 1000: 0, c2: 0 }, _success: false },
+{ constants: { 数: 0 }, _success: true },
+{ constants: { séquençage: 0 }, _success: false } // test unicode is not normalized
+])
+).
+fn((t) => {
+ const { isAsync, constants, _success } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ override c0: bool = true; // type: bool
+ override c1: u32 = 0u; // default override
+ override 数: u32 = 0u; // non-ASCII
+ override séquençage: u32 = 0u; // normalizable unicode (WGSL does not normalize)
+ @id(1000) override c2: u32 = 10u; // default
+ @id(1) override c3: u32 = 11u; // default
+ @compute @workgroup_size(1) fn main () {
+ // make sure the overridable constants are not optimized out
+ _ = u32(c0);
+ _ = u32(c1);
+ _ = u32(c2 + séquençage);
+ _ = u32(c3 + 数);
+ }`
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('overrides,uninitialized').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for uninitialized overridable constants.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ constants: {}, _success: false },
+{ constants: { c0: 0, c2: 0, c8: 0 }, _success: false }, // c5 is missing
+{ constants: { c0: 0, c2: 0, c5: 0, c8: 0 }, _success: true },
+{ constants: { c0: 0, c2: 0, c5: 0, c8: 0, c1: 0 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, constants, _success } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ override c0: bool; // type: bool
+ override c1: bool = false; // default override
+ override c2: f32; // type: float32
+ override c3: f32 = 0.0; // default override
+ override c4: f32 = 4.0; // default
+ override c5: i32; // type: int32
+ override c6: i32 = 0; // default override
+ override c7: i32 = 7; // default
+ override c8: u32; // type: uint32
+ override c9: u32 = 0u; // default override
+ @id(1000) override c10: u32 = 10u; // default
+ @compute @workgroup_size(1) fn main () {
+ // make sure the overridable constants are not optimized out
+ _ = u32(c0);
+ _ = u32(c1);
+ _ = u32(c2);
+ _ = u32(c3);
+ _ = u32(c4);
+ _ = u32(c5);
+ _ = u32(c6);
+ _ = u32(c7);
+ _ = u32(c8);
+ _ = u32(c9);
+ _ = u32(c10);
+ }`
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('overrides,value,type_error').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for constant values like inf, NaN will results in TypeError.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ constants: { cf: 1 }, _success: true }, // control
+{ constants: { cf: NaN }, _success: false },
+{ constants: { cf: Number.POSITIVE_INFINITY }, _success: false },
+{ constants: { cf: Number.NEGATIVE_INFINITY }, _success: false }]
+)
+).
+fn((t) => {
+ const { isAsync, constants, _success } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ override cf: f32 = 0.0;
+ @compute @workgroup_size(1) fn main () {
+ _ = cf;
+ }`
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor, 'TypeError');
+});
+
+g.test('overrides,value,validation_error').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for unrepresentable constant values in compute stage.
+
+TODO(#2060): test with last_castable_pipeline_override.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ constants: { cu: kValue.u32.min }, _success: true },
+{ constants: { cu: kValue.u32.min - 1 }, _success: false },
+{ constants: { cu: kValue.u32.max }, _success: true },
+{ constants: { cu: kValue.u32.max + 1 }, _success: false },
+{ constants: { ci: kValue.i32.negative.min }, _success: true },
+{ constants: { ci: kValue.i32.negative.min - 1 }, _success: false },
+{ constants: { ci: kValue.i32.positive.max }, _success: true },
+{ constants: { ci: kValue.i32.positive.max + 1 }, _success: false },
+{ constants: { cf: kValue.f32.negative.min }, _success: true },
+{
+ constants: { cf: kValue.f32.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{ constants: { cf: kValue.f32.positive.max }, _success: true },
+{
+ constants: { cf: kValue.f32.positive.first_non_castable_pipeline_override },
+ _success: false
+},
+// Conversion to boolean can't fail
+{ constants: { cb: Number.MAX_VALUE }, _success: true },
+{ constants: { cb: kValue.i32.negative.min - 1 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, constants, _success } = t.params;
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ override cb: bool = false;
+ override cu: u32 = 0u;
+ override ci: i32 = 0;
+ override cf: f32 = 0.0;
+ @compute @workgroup_size(1) fn main () {
+ _ = cb;
+ _ = cu;
+ _ = ci;
+ _ = cf;
+ }`
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('overrides,value,validation_error,f16').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for unrepresentable f16 constant values in compute stage.
+
+TODO(#2060): Tighten the cases around the valid/invalid boundary once we have WGSL spec
+clarity on whether values like f16.positive.last_castable_pipeline_override would be valid. See issue.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ constants: { cf16: kValue.f16.negative.min }, _success: true },
+{
+ constants: { cf16: kValue.f16.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{ constants: { cf16: kValue.f16.positive.max }, _success: true },
+{
+ constants: { cf16: kValue.f16.positive.first_non_castable_pipeline_override },
+ _success: false
+},
+{ constants: { cf16: kValue.f32.negative.min }, _success: false },
+{ constants: { cf16: kValue.f32.positive.max }, _success: false },
+{
+ constants: { cf16: kValue.f32.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{
+ constants: { cf16: kValue.f32.positive.first_non_castable_pipeline_override },
+ _success: false
+}]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn((t) => {
+ const { isAsync, constants, _success } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ enable f16;
+
+ override cf16: f16 = 0.0h;
+ @compute @workgroup_size(1) fn main () {
+ _ = cf16;
+ }`
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+const kOverridesWorkgroupSizeShaders = {
+ u32: `
+override x: u32 = 1u;
+override y: u32 = 1u;
+override z: u32 = 1u;
+@compute @workgroup_size(x, y, z) fn main () {
+ _ = 0u;
+}
+`,
+ i32: `
+override x: i32 = 1;
+override y: i32 = 1;
+override z: i32 = 1;
+@compute @workgroup_size(x, y, z) fn main () {
+ _ = 0u;
+}
+`
+};
+
+g.test('overrides,workgroup_size').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for overridable constants used for workgroup size.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combine('type', ['u32', 'i32']).
+combineWithParams([
+{ constants: {}, _success: true },
+{ constants: { x: 0, y: 0, z: 0 }, _success: false },
+{ constants: { x: 1, y: -1, z: 1 }, _success: false },
+{ constants: { x: 1, y: 0, z: 0 }, _success: false },
+{ constants: { x: 16, y: 1, z: 1 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, type, constants, _success } = t.params;
+
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: kOverridesWorkgroupSizeShaders[type]
+ }),
+ entryPoint: 'main',
+ constants
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('overrides,workgroup_size,limits').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for overridable constants for workgroupSize exceeds device limits.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combine('type', ['u32', 'i32'])
+).
+fn((t) => {
+ const { isAsync, type } = t.params;
+
+ const limits = t.device.limits;
+
+ const testFn = (x, y, z, _success) => {
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: kOverridesWorkgroupSizeShaders[type]
+ }),
+ entryPoint: 'main',
+ constants: {
+ x,
+ y,
+ z
+ }
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+ };
+
+ testFn(limits.maxComputeWorkgroupSizeX, 1, 1, true);
+ testFn(limits.maxComputeWorkgroupSizeX + 1, 1, 1, false);
+ testFn(1, limits.maxComputeWorkgroupSizeY, 1, true);
+ testFn(1, limits.maxComputeWorkgroupSizeY + 1, 1, false);
+ testFn(1, 1, limits.maxComputeWorkgroupSizeZ, true);
+ testFn(1, 1, limits.maxComputeWorkgroupSizeZ + 1, false);
+ testFn(
+ limits.maxComputeWorkgroupSizeX,
+ limits.maxComputeWorkgroupSizeY,
+ limits.maxComputeWorkgroupSizeZ,
+ limits.maxComputeWorkgroupSizeX *
+ limits.maxComputeWorkgroupSizeY *
+ limits.maxComputeWorkgroupSizeZ <=
+ limits.maxComputeInvocationsPerWorkgroup
+ );
+});
+
+g.test('overrides,workgroup_size,limits,workgroup_storage_size').
+desc(
+ `
+Tests calling createComputePipeline(Async) validation for overridable constants for workgroupStorageSize exceeds device limits.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false])
+).
+fn((t) => {
+ const { isAsync } = t.params;
+
+ const limits = t.device.limits;
+
+ const kVec4Size = 16;
+ const maxVec4Count = limits.maxComputeWorkgroupStorageSize / kVec4Size;
+ const kMat4Size = 64;
+ const maxMat4Count = limits.maxComputeWorkgroupStorageSize / kMat4Size;
+
+ const testFn = (vec4Count, mat4Count, _success) => {
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ override a: u32;
+ override b: u32;
+ ${vec4Count <= 0 ? '' : 'var<workgroup> vec4_data: array<vec4<f32>, a>;'}
+ ${mat4Count <= 0 ? '' : 'var<workgroup> mat4_data: array<mat4x4<f32>, b>;'}
+ @compute @workgroup_size(1) fn main() {
+ ${vec4Count <= 0 ? '' : '_ = vec4_data[0];'}
+ ${mat4Count <= 0 ? '' : '_ = mat4_data[0];'}
+ }`
+ }),
+ entryPoint: 'main',
+ constants: {
+ a: vec4Count,
+ b: mat4Count
+ }
+ }
+ };
+
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+ };
+
+ testFn(1, 1, true);
+ testFn(maxVec4Count + 1, 0, false);
+ testFn(0, maxMat4Count + 1, false);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroup.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroup.spec.js
new file mode 100644
index 0000000000..15691f4bf7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroup.spec.js
@@ -0,0 +1,1110 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+ createBindGroup validation tests.
+
+ TODO: Ensure sure tests cover all createBindGroup validation rules.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js';
+import {
+ allBindingEntries,
+ bindingTypeInfo,
+ bufferBindingEntries,
+ bufferBindingTypeInfo,
+ kBindableResources,
+ kBufferBindingTypes,
+ kBufferUsages,
+ kCompareFunctions,
+ kSamplerBindingTypes,
+ kTextureUsages,
+ kTextureViewDimensions,
+ sampledAndStorageBindingEntries,
+ texBindingTypeInfo } from
+'../../capability_info.js';
+import { GPUConst } from '../../constants.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../format_info.js';
+import { kResourceStates } from '../../gpu_test.js';
+import { getTextureDimensionFromView } from '../../util/texture/base.js';
+
+import { ValidationTest } from './validation_test.js';
+
+function clone(descriptor) {
+ return JSON.parse(JSON.stringify(descriptor));
+}
+
+export const g = makeTestGroup(ValidationTest);
+
+const kStorageTextureFormats = kAllTextureFormats.filter((f) => kTextureFormatInfo[f].color?.storage);
+
+g.test('binding_count_mismatch').
+desc('Test that the number of entries must match the number of entries in the BindGroupLayout.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('layoutEntryCount', [1, 2, 3]).
+combine('bindGroupEntryCount', [1, 2, 3])
+).
+fn((t) => {
+ const { layoutEntryCount, bindGroupEntryCount } = t.params;
+
+ const layoutEntries = [];
+ for (let i = 0; i < layoutEntryCount; ++i) {
+ layoutEntries.push({
+ binding: i,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'storage' }
+ });
+ }
+ const bindGroupLayout = t.device.createBindGroupLayout({ entries: layoutEntries });
+
+ const entries = [];
+ for (let i = 0; i < bindGroupEntryCount; ++i) {
+ entries.push({
+ binding: i,
+ resource: { buffer: t.getStorageBuffer() }
+ });
+ }
+
+ const shouldError = layoutEntryCount !== bindGroupEntryCount;
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries,
+ layout: bindGroupLayout
+ });
+ }, shouldError);
+});
+
+g.test('binding_must_be_present_in_layout').
+desc(
+ 'Test that the binding slot for each entry matches a binding slot defined in the BindGroupLayout.'
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('layoutBinding', [0, 1, 2]).
+combine('binding', [0, 1, 2])
+).
+fn((t) => {
+ const { layoutBinding, binding } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ { binding: layoutBinding, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } }]
+
+ });
+
+ const descriptor = {
+ entries: [{ binding, resource: { buffer: t.getStorageBuffer() } }],
+ layout: bindGroupLayout
+ };
+
+ const shouldError = layoutBinding !== binding;
+ t.expectValidationError(() => {
+ t.device.createBindGroup(descriptor);
+ }, shouldError);
+});
+
+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) =>
+u //
+.combine('resourceType', kBindableResources).
+combine('entry', allBindingEntries(false))
+).
+fn((t) => {
+ const { resourceType, entry } = t.params;
+ const info = bindingTypeInfo(entry);
+
+ const layout = t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, ...entry }]
+ });
+
+ const resource = t.getBindingResource(resourceType);
+
+ let resourceBindingIsCompatible;
+ switch (info.resource) {
+ // Either type of sampler may be bound to a filtering sampler binding.
+ case 'filtSamp':
+ resourceBindingIsCompatible = resourceType === 'filtSamp' || resourceType === 'nonFiltSamp';
+ break;
+ // But only non-filtering samplers can be used with non-filtering sampler bindings.
+ case 'nonFiltSamp':
+ resourceBindingIsCompatible = resourceType === 'nonFiltSamp';
+ break;
+ default:
+ resourceBindingIsCompatible = info.resource === resourceType;
+ break;
+ }
+ t.expectValidationError(() => {
+ t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] });
+ }, !resourceBindingIsCompatible);
+});
+
+g.test('texture_binding_must_have_correct_usage').
+desc('Tests that texture bindings must have the correct usage.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('entry', sampledAndStorageBindingEntries(false)).
+combine('usage', kTextureUsages).
+unless(({ entry, usage }) => {
+ const info = texBindingTypeInfo(entry);
+ // Can't create the texture for this (usage=STORAGE_BINDING and sampleCount=4), so skip.
+ return usage === GPUConst.TextureUsage.STORAGE_BINDING && info.resource === 'sampledTexMS';
+})
+).
+fn((t) => {
+ const { entry, usage } = t.params;
+ const info = texBindingTypeInfo(entry);
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, ...entry }]
+ });
+
+ // The `RENDER_ATTACHMENT` usage must be specified if sampleCount > 1 according to WebGPU SPEC.
+ const appliedUsage =
+ info.resource === 'sampledTexMS' ? usage | GPUConst.TextureUsage.RENDER_ATTACHMENT : usage;
+
+ const descriptor = {
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: appliedUsage,
+ sampleCount: info.resource === 'sampledTexMS' ? 4 : 1
+ };
+ const resource = t.device.createTexture(descriptor).createView();
+
+ const shouldError = (usage & info.usage) === 0;
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource }],
+ layout: bindGroupLayout
+ });
+ }, shouldError);
+});
+
+g.test('texture_must_have_correct_component_type').
+desc(
+ `
+ Tests that texture bindings must have a format that matches the sample type specified in the BindGroupLayout.
+ - Tests a compatible format for every sample type
+ - Tests an incompatible format for every sample type`
+).
+params((u) => u.combine('sampleType', ['float', 'sint', 'uint'])).
+fn((t) => {
+ const { sampleType } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: { sampleType }
+ }]
+
+ });
+
+ let format;
+ if (sampleType === 'float') {
+ format = 'r8unorm';
+ } else if (sampleType === 'sint') {
+ format = 'r8sint';
+ } else if (sampleType === 'uint') {
+ format = 'r8uint';
+ } else {
+ unreachable('Unexpected texture component type');
+ }
+
+ const goodDescriptor = {
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ // Control case
+ t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: t.device.createTexture(goodDescriptor).createView()
+ }],
+
+ layout: bindGroupLayout
+ });
+
+ function* mismatchedTextureFormats() {
+ if (sampleType !== 'float') {
+ yield 'r8unorm';
+ }
+ if (sampleType !== 'sint') {
+ yield 'r8sint';
+ }
+ if (sampleType !== 'uint') {
+ yield 'r8uint';
+ }
+ }
+
+ // Mismatched texture binding formats are not valid.
+ for (const mismatchedTextureFormat of mismatchedTextureFormats()) {
+ const badDescriptor = clone(goodDescriptor);
+ badDescriptor.format = mismatchedTextureFormat;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
+ layout: bindGroupLayout
+ });
+ });
+ }
+});
+
+g.test('texture_must_have_correct_dimension').
+desc(
+ `
+ Test that bound texture views match the dimensions supplied in the BindGroupLayout
+ - Test for every GPUTextureViewDimension
+ - Test for both TEXTURE_BINDING and STORAGE_BINDING.
+ `
+).
+params((u) =>
+u.
+combine('usage', [
+GPUConst.TextureUsage.TEXTURE_BINDING,
+GPUConst.TextureUsage.STORAGE_BINDING]
+).
+combine('viewDimension', kTextureViewDimensions).
+unless(
+ (p) =>
+ p.usage === GPUConst.TextureUsage.STORAGE_BINDING && (
+ p.viewDimension === 'cube' || p.viewDimension === 'cube-array')
+).
+beginSubcases().
+combine('dimension', kTextureViewDimensions)
+).
+fn((t) => {
+ const { usage, viewDimension, dimension } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ usage === GPUTextureUsage.TEXTURE_BINDING ?
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: { viewDimension }
+ } :
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ storageTexture: { access: 'write-only', format: 'rgba8unorm', viewDimension }
+ }]
+
+ });
+
+ let height = 16;
+ let depthOrArrayLayers = 6;
+ if (dimension === '1d') {
+ height = 1;
+ depthOrArrayLayers = 1;
+ }
+
+ const texture = t.device.createTexture({
+ size: { width: 16, height, depthOrArrayLayers },
+ format: 'rgba8unorm',
+ usage,
+ dimension: getTextureDimensionFromView(dimension)
+ });
+
+ t.skipIfTextureViewDimensionNotSupported(viewDimension, dimension);
+
+ const shouldError = viewDimension !== dimension;
+ const textureView = texture.createView({ dimension });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: textureView }],
+ layout: bindGroupLayout
+ });
+ }, shouldError);
+});
+
+g.test('multisampled_validation').
+desc(
+ `
+ Test that the sample count of the texture is greater than 1 if the BindGroup entry's
+ multisampled is true. Otherwise, the texture's sampleCount should be 1.
+ `
+).
+params((u) =>
+u //
+.combine('multisampled', [true, false]).
+beginSubcases().
+combine('sampleCount', [1, 4])
+).
+fn((t) => {
+ const { multisampled, sampleCount } = t.params;
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: { multisampled, sampleType: multisampled ? 'unfilterable-float' : undefined }
+ }]
+
+ });
+
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount
+ });
+
+ const isValid = !multisampled && sampleCount === 1 || multisampled && sampleCount > 1;
+
+ const textureView = texture.createView();
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: textureView }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('buffer_offset_and_size_for_bind_groups_match').
+desc(
+ `
+ Test that a buffer binding's [offset, offset + size) must be contained in the BindGroup entry's buffer.
+ - Test for various offsets and sizes`
+).
+paramsSubcasesOnly([
+{ offset: 0, size: 512, _success: true }, // offset 0 is valid
+{ offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid
+
+// Touching the end of the buffer
+{ offset: 0, size: 1024, _success: true },
+{ offset: 0, size: undefined, _success: true },
+{ offset: 256 * 3, size: 256, _success: true },
+{ offset: 256 * 3, size: undefined, _success: true },
+
+// Zero-sized bindings
+{ offset: 0, size: 0, _success: false },
+{ offset: 256, size: 0, _success: false },
+{ offset: 1024, size: 0, _success: false },
+{ offset: 1024, size: undefined, _success: false },
+
+// Unaligned buffer offset is invalid
+{ offset: 1, size: 256, _success: false },
+{ offset: 1, size: undefined, _success: false },
+{ offset: 128, size: 256, _success: false },
+{ offset: 255, size: 256, _success: false },
+
+// Out-of-bounds
+{ offset: 256 * 5, size: 0, _success: false }, // offset is OOB
+{ offset: 0, size: 256 * 5, _success: false }, // size is OOB
+{ offset: 1024, size: 1, _success: false } // offset+size is OOB
+]).
+fn((t) => {
+ const { offset, size, _success } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } }]
+ });
+
+ const buffer = t.device.createBuffer({
+ size: 1024,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ const descriptor = {
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer, offset, size }
+ }],
+
+ layout: bindGroupLayout
+ };
+
+ if (_success) {
+ // Control case
+ t.device.createBindGroup(descriptor);
+ } else {
+ // Buffer offset and/or size don't match in bind groups.
+ t.expectValidationError(() => {
+ t.device.createBindGroup(descriptor);
+ });
+ }
+});
+
+g.test('minBindingSize').
+desc('Tests that minBindingSize is correctly enforced.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('minBindingSize', [undefined, 4, 8, 256]).
+expand('size', ({ minBindingSize }) =>
+minBindingSize !== undefined ?
+[minBindingSize - 4, minBindingSize, minBindingSize + 4] :
+[4, 256]
+)
+).
+fn((t) => {
+ const { size, minBindingSize } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ buffer: {
+ type: 'storage',
+ minBindingSize
+ }
+ }]
+
+ });
+
+ const storageBuffer = t.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ t.expectValidationError(
+ () => {
+ t.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: storageBuffer }
+ }]
+
+ });
+ },
+ minBindingSize !== undefined && size < minBindingSize
+ );
+});
+
+g.test('buffer,resource_state').
+desc('Test bind group creation with various buffer resource states').
+paramsSubcasesOnly((u) =>
+u.combine('state', kResourceStates).combine('entry', bufferBindingEntries(true))
+).
+fn((t) => {
+ const { state, entry } = t.params;
+
+ assert(entry.buffer !== undefined);
+ const info = bufferBindingTypeInfo(entry.buffer);
+
+ const bgl = t.device.createBindGroupLayout({
+ entries: [
+ {
+ ...entry,
+ binding: 0,
+ visibility: info.validStages
+ }]
+
+ });
+
+ const buffer = t.createBufferWithState(state, {
+ usage: info.usage,
+ size: 4
+ });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer
+ }
+ }]
+
+ });
+ }, state === 'invalid');
+});
+
+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'))
+).
+fn((t) => {
+ const { state, entry } = t.params;
+ const info = texBindingTypeInfo(entry);
+
+ const bgl = t.device.createBindGroupLayout({
+ entries: [
+ {
+ ...entry,
+ binding: 0,
+ visibility: info.validStages
+ }]
+
+ });
+
+ // The `RENDER_ATTACHMENT` usage must be specified if sampleCount > 1 according to WebGPU SPEC.
+ const usage = entry.texture?.multisampled ?
+ info.usage | GPUConst.TextureUsage.RENDER_ATTACHMENT :
+ info.usage;
+ const texture = t.createTextureWithState(state, {
+ usage,
+ size: [1, 1],
+ format: 'rgba8unorm',
+ sampleCount: entry.texture?.multisampled ? 4 : 1
+ });
+
+ let textureView;
+ t.expectValidationError(() => {
+ textureView = texture.createView();
+ }, state === 'invalid');
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ {
+ binding: 0,
+ resource: textureView
+ }]
+
+ });
+ }, state === 'invalid');
+});
+
+g.test('bind_group_layout,device_mismatch').
+desc(
+ 'Tests createBindGroup cannot be called with a bind group layout created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const mismatched = t.params.mismatched;
+
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const bgl = sourceDevice.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUConst.ShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer: t.getUniformBuffer() }
+ }]
+
+ });
+ }, mismatched);
+});
+
+g.test('binding_resources,device_mismatch').
+desc(
+ `
+ Tests createBindGroup cannot be called with various resources created from another device
+ Test with two resources to make sure all resources can be validated:
+ - resource0 and resource1 from same device
+ - resource0 and resource1 from different device
+
+ TODO: test GPUExternalTexture as a resource
+ `
+).
+params((u) =>
+u.
+combine('entry', [
+{ buffer: { type: 'storage' } },
+{ sampler: { type: 'filtering' } },
+{ texture: { multisampled: false } },
+{ storageTexture: { access: 'write-only', format: 'rgba8unorm' } }]
+).
+beginSubcases().
+combineWithParams([
+{ resource0Mismatched: false, resource1Mismatched: false }, //control case
+{ resource0Mismatched: true, resource1Mismatched: false },
+{ resource0Mismatched: false, resource1Mismatched: true }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { entry, resource0Mismatched, resource1Mismatched } = t.params;
+
+ const info = bindingTypeInfo(entry);
+
+ const resource0 = resource0Mismatched ?
+ t.getDeviceMismatchedBindingResource(info.resource) :
+ t.getBindingResource(info.resource);
+ const resource1 = resource1Mismatched ?
+ t.getDeviceMismatchedBindingResource(info.resource) :
+ t.getBindingResource(info.resource);
+
+ const bgl = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: info.validStages,
+ ...entry
+ },
+ {
+ binding: 1,
+ visibility: info.validStages,
+ ...entry
+ }]
+
+ });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ {
+ binding: 0,
+ resource: resource0
+ },
+ {
+ binding: 1,
+ resource: resource1
+ }]
+
+ });
+ }, resource0Mismatched || resource1Mismatched);
+});
+
+g.test('storage_texture,usage').
+desc(
+ `
+ Test that the texture usage contains STORAGE_BINDING if the BindGroup entry defines
+ storageTexture.
+ `
+).
+params((u) =>
+u //
+// If usage0 and usage1 are the same, the usage being test is a single usage. Otherwise, it's
+// a combined usage.
+.combine('usage0', kTextureUsages).
+combine('usage1', kTextureUsages)
+).
+fn((t) => {
+ const { usage0, usage1 } = t.params;
+
+ const usage = usage0 | usage1;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ storageTexture: { access: 'write-only', format: 'rgba8unorm' }
+ }]
+
+ });
+
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage
+ });
+
+ const isValid = GPUTextureUsage.STORAGE_BINDING & usage;
+
+ const textureView = texture.createView();
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: textureView }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('storage_texture,mip_level_count').
+desc(
+ `
+ Test that the mip level count of the resource of the BindGroup entry as a descriptor is 1 if the
+ BindGroup entry defines storageTexture. If the mip level count is not 1, a validation error
+ should be generated.
+ `
+).
+params((u) =>
+u //
+.combine('baseMipLevel', [1, 2]).
+combine('mipLevelCount', [1, 2])
+).
+fn((t) => {
+ const { baseMipLevel, mipLevelCount } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ storageTexture: { access: 'write-only', format: 'rgba8unorm' }
+ }]
+
+ });
+
+ const MIP_LEVEL_COUNT = 4;
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ mipLevelCount: MIP_LEVEL_COUNT
+ });
+
+ const textureView = texture.createView({ baseMipLevel, mipLevelCount });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: textureView }],
+ layout: bindGroupLayout
+ });
+ }, mipLevelCount !== 1);
+});
+
+g.test('storage_texture,format').
+desc(
+ `
+ Test that the format of the storage texture is equal to resource's descriptor format if the
+ BindGroup entry defines storageTexture.
+ `
+).
+params((u) =>
+u //
+.combine('storageTextureFormat', kStorageTextureFormats).
+combine('resourceFormat', kStorageTextureFormats)
+).
+fn((t) => {
+ const { storageTextureFormat, resourceFormat } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ storageTexture: { access: 'write-only', format: storageTextureFormat }
+ }]
+
+ });
+
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: resourceFormat,
+ usage: GPUTextureUsage.STORAGE_BINDING
+ });
+
+ const isValid = storageTextureFormat === resourceFormat;
+ const textureView = texture.createView({ format: resourceFormat });
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: textureView }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('buffer,usage').
+desc(
+ `
+ Test that the buffer usage contains 'UNIFORM' if the BindGroup entry defines buffer and it's
+ type is 'uniform', and the buffer usage contains 'STORAGE' if the BindGroup entry's buffer type
+ is 'storage'|read-only-storage'.
+ `
+).
+params((u) =>
+u //
+.combine('type', kBufferBindingTypes)
+// If usage0 and usage1 are the same, the usage being test is a single usage. Otherwise, it's
+// a combined usage.
+.beginSubcases().
+combine('usage0', kBufferUsages).
+combine('usage1', kBufferUsages).
+unless(
+ ({ usage0, usage1 }) =>
+ ((usage0 | usage1) & (GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE)) !==
+ 0
+)
+).
+fn((t) => {
+ const { type, usage0, usage1 } = t.params;
+
+ const usage = usage0 | usage1;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type }
+ }]
+
+ });
+
+ const buffer = t.device.createBuffer({
+ size: 4,
+ usage
+ });
+
+ let isValid = false;
+ if (type === 'uniform') {
+ isValid = GPUBufferUsage.UNIFORM & usage ? true : false;
+ } else if (type === 'storage' || type === 'read-only-storage') {
+ isValid = GPUBufferUsage.STORAGE & usage ? true : false;
+ }
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer } }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('buffer,resource_offset').
+desc(
+ `
+ Test that the resource.offset of the BindGroup entry is a multiple of limits.
+ 'minUniformBufferOffsetAlignment|minStorageBufferOffsetAlignment' if the BindGroup entry defines
+ buffer and the buffer type is 'uniform|storage|read-only-storage'.
+ `
+).
+params((u) =>
+u //
+.combine('type', kBufferBindingTypes).
+beginSubcases().
+combine('offsetAddMult', [
+{ add: 0, mult: 0 },
+{ add: 0, mult: 0.5 },
+{ add: 0, mult: 1.5 },
+{ add: 2, mult: 0 }]
+)
+).
+fn((t) => {
+ const { type, offsetAddMult } = t.params;
+ const minAlignment =
+ t.device.limits[
+ type === 'uniform' ? 'minUniformBufferOffsetAlignment' : 'minStorageBufferOffsetAlignment'];
+
+ const offset = makeValueTestVariant(minAlignment, offsetAddMult);
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type }
+ }]
+
+ });
+
+ const usage = type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE;
+ const isValid = offset % minAlignment === 0;
+
+ const buffer = t.device.createBuffer({
+ size: 1024,
+ usage
+ });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer, offset } }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('buffer,resource_binding_size').
+desc(
+ `
+ Test that the buffer binding size of the BindGroup entry is equal to or less than limits.
+ 'maxUniformBufferBindingSize|maxStorageBufferBindingSize' if the BindGroup entry defines
+ buffer and the buffer type is 'uniform|storage|read-only-storage'.
+ `
+).
+params((u) =>
+u.
+combine('type', kBufferBindingTypes).
+beginSubcases()
+// Test a size of 1 (for uniform buffer) or 4 (for storage and read-only storage buffer)
+// then values just within and just above the limit.
+.combine('bindingSize', [
+{ base: 1, limit: 0 },
+{ base: 0, limit: 1 },
+{ base: 1, limit: 1 }]
+)
+).
+fn((t) => {
+ const {
+ type,
+ bindingSize: { base, limit }
+ } = t.params;
+ const mult = type === 'uniform' ? 1 : 4;
+ const maxBindingSize =
+ t.device.limits[
+ type === 'uniform' ? 'maxUniformBufferBindingSize' : 'maxStorageBufferBindingSize'];
+
+ const bindingSize = base * mult + maxBindingSize * limit;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type }
+ }]
+
+ });
+
+ const usage = type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE;
+ const isValid = bindingSize <= maxBindingSize;
+
+ // MAINTENANCE_TODO: Allocating the max size seems likely to fail. Refactor test.
+ const buffer = t.device.createBuffer({
+ size: maxBindingSize,
+ usage
+ });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer, size: bindingSize } }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('buffer,effective_buffer_binding_size').
+desc(
+ `
+ Test that the effective buffer binding size of the BindGroup entry must be a multiple of 4 if the
+ buffer type is 'storage|read-only-storage', while there is no such restriction on uniform buffers.
+`
+).
+params((u) =>
+u.
+combine('type', kBufferBindingTypes).
+beginSubcases().
+combine('offsetMult', [0, 1]).
+combine('bufferSizeAddition', [8, 10]).
+combine('bindingSize', [undefined, 2, 4, 6])
+).
+fn((t) => {
+ const { type, offsetMult, bufferSizeAddition, bindingSize } = t.params;
+ const minAlignment =
+ t.device.limits[
+ type === 'uniform' ? 'minUniformBufferOffsetAlignment' : 'minStorageBufferOffsetAlignment'];
+
+ const offset = minAlignment * offsetMult;
+ const bufferSize = minAlignment + bufferSizeAddition;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type }
+ }]
+
+ });
+
+ const effectiveBindingSize = bindingSize ?? bufferSize - offset;
+ let usage, isValid;
+ if (type === 'uniform') {
+ usage = GPUBufferUsage.UNIFORM;
+ isValid = true;
+ } else {
+ usage = GPUBufferUsage.STORAGE;
+ isValid = effectiveBindingSize % 4 === 0;
+ }
+
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage
+ });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer, offset, size: bindingSize } }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+});
+
+g.test('sampler,device_mismatch').
+desc(`Tests createBindGroup cannot be called with a sampler created from another device.`).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ sampler: { type: 'filtering' }
+ }]
+
+ });
+
+ const sampler = sourceDevice.createSampler();
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: sampler }],
+ layout: bindGroupLayout
+ });
+ }, mismatched);
+});
+
+g.test('sampler,compare_function_with_binding_type').
+desc(
+ `
+ Test that the sampler of the BindGroup has a 'compareFunction' value if the sampler type of the
+ BindGroupLayout is 'comparison'. Other sampler types should not have 'compare' field in
+ the descriptor of the sampler.
+ `
+).
+params((u) =>
+u //
+.combine('bgType', kSamplerBindingTypes).
+beginSubcases().
+combine('compareFunction', [undefined, ...kCompareFunctions])
+).
+fn((t) => {
+ const { bgType, compareFunction } = t.params;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ sampler: { type: bgType }
+ }]
+
+ });
+
+ const isValid =
+ bgType === 'comparison' ? compareFunction !== undefined : compareFunction === undefined;
+
+ const sampler = t.device.createSampler({ compare: compareFunction });
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: sampler }],
+ layout: bindGroupLayout
+ });
+ }, !isValid);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js
new file mode 100644
index 0000000000..580a64af4f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createBindGroupLayout.spec.js
@@ -0,0 +1,464 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+createBindGroupLayout validation tests.
+
+TODO: make sure tests are complete.
+`;import { kUnitCaseParamsBuilder } from '../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import {
+ kShaderStages,
+ kShaderStageCombinations,
+ kStorageTextureAccessValues,
+ kTextureSampleTypes,
+ kTextureViewDimensions,
+ allBindingEntries,
+ bindingTypeInfo,
+ bufferBindingTypeInfo,
+ kBufferBindingTypes } from
+
+'../../capability_info.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../format_info.js';
+
+import { ValidationTest } from './validation_test.js';
+
+function clone(descriptor) {
+ return JSON.parse(JSON.stringify(descriptor));
+}
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('duplicate_bindings').
+desc('Test that uniqueness of binding numbers across entries is enforced.').
+paramsSubcasesOnly([
+{ bindings: [0, 1], _valid: true },
+{ bindings: [0, 0], _valid: false }]
+).
+fn((t) => {
+ const { bindings, _valid } = t.params;
+ const entries = [];
+
+ for (const binding of bindings) {
+ entries.push({
+ binding,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'storage' }
+ });
+ }
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries
+ });
+ }, !_valid);
+});
+
+g.test('maximum_binding_limit').
+desc(
+ `
+ Test that a validation error is generated if the binding number exceeds the maximum binding limit.
+
+ TODO: Need to also test with higher limits enabled on the device, once we have a way to do that.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.combine('bindingVariant', [1, 4, 8, 256, 'default', 'default-minus-one'])
+).
+fn((t) => {
+ const { bindingVariant } = t.params;
+ const entries = [];
+
+ const binding =
+ bindingVariant === 'default' ?
+ t.device.limits.maxBindingsPerBindGroup :
+ bindingVariant === 'default-minus-one' ?
+ t.device.limits.maxBindingsPerBindGroup - 1 :
+ bindingVariant;
+
+ entries.push({
+ binding,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'storage' }
+ });
+
+ const success = binding < t.device.limits.maxBindingsPerBindGroup;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries
+ });
+ }, !success);
+});
+
+g.test('visibility').
+desc(
+ `
+ Test that only the appropriate combinations of visibilities are allowed for each resource type.
+ - Test each possible combination of shader stage visibilities.
+ - Test each type of bind group resource.`
+).
+params((u) =>
+u.
+combine('visibility', kShaderStageCombinations).
+beginSubcases().
+combine('entry', allBindingEntries(false))
+).
+fn((t) => {
+ const { visibility, entry } = t.params;
+ const info = bindingTypeInfo(entry);
+
+ const success = (visibility & ~info.validStages) === 0;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility, ...entry }]
+ });
+ }, !success);
+});
+
+g.test('visibility,VERTEX_shader_stage_buffer_type').
+desc(
+ `
+ Test that a validation error is generated if the buffer type is 'storage' when the
+ visibility of the entry includes VERTEX.
+ `
+).
+params((u) =>
+u //
+.combine('shaderStage', kShaderStageCombinations).
+beginSubcases().
+combine('type', kBufferBindingTypes)
+).
+fn((t) => {
+ const { shaderStage, type } = t.params;
+
+ const success = !(type === 'storage' && shaderStage & GPUShaderStage.VERTEX);
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: shaderStage,
+ buffer: { type }
+ }]
+
+ });
+ }, !success);
+});
+
+g.test('visibility,VERTEX_shader_stage_storage_texture_access').
+desc(
+ `
+ Test that a validation error is generated if the access value is 'write-only' when the
+ visibility of the entry includes VERTEX.
+ `
+).
+params((u) =>
+u //
+.combine('shaderStage', kShaderStageCombinations).
+beginSubcases().
+combine('access', [undefined, ...kStorageTextureAccessValues])
+).
+fn((t) => {
+ const { shaderStage, access } = t.params;
+
+ const success = !(
+ (access ?? 'write-only') === 'write-only' && shaderStage & GPUShaderStage.VERTEX);
+
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: shaderStage,
+ storageTexture: { access, format: 'rgba8unorm' }
+ }]
+
+ });
+ }, !success);
+});
+
+g.test('multisampled_validation').
+desc(
+ `
+ Test that multisampling is only allowed if view dimensions is "2d" and the sampleType is not
+ "float".
+ `
+).
+params((u) =>
+u //
+.combine('viewDimension', [undefined, ...kTextureViewDimensions]).
+beginSubcases().
+combine('sampleType', [undefined, ...kTextureSampleTypes])
+).
+fn((t) => {
+ const { viewDimension, sampleType } = t.params;
+
+ const success =
+ (viewDimension === '2d' || viewDimension === undefined) &&
+ (sampleType ?? 'float') !== 'float';
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ texture: { multisampled: true, viewDimension, sampleType }
+ }]
+
+ });
+ }, !success);
+});
+
+g.test('max_dynamic_buffers').
+desc(
+ `
+ Test that limits on the maximum number of dynamic buffers are enforced.
+ - Test creation of a bind group layout using the maximum number of dynamic buffers works.
+ - Test creation of a bind group layout using the maximum number of dynamic buffers + 1 fails.
+ - TODO(#230): Update to enforce per-stage and per-pipeline-layout limits on BGLs as well.`
+).
+params((u) =>
+u.
+combine('type', kBufferBindingTypes).
+beginSubcases().
+combine('extraDynamicBuffers', [0, 1]).
+combine('staticBuffers', [0, 1])
+).
+fn((t) => {
+ const { type, extraDynamicBuffers, staticBuffers } = t.params;
+ const info = bufferBindingTypeInfo({ type });
+
+ const limitName = info.perPipelineLimitClass.maxDynamicLimit;
+ const bufferCount = limitName ? t.getDefaultLimit(limitName) : 0;
+ const dynamicBufferCount = bufferCount + extraDynamicBuffers;
+ const perStageLimit = t.getDefaultLimit(info.perStageLimitClass.maxLimit);
+
+ const entries = [];
+ for (let i = 0; i < dynamicBufferCount; i++) {
+ entries.push({
+ binding: i,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type, hasDynamicOffset: true }
+ });
+ }
+
+ for (let i = dynamicBufferCount; i < dynamicBufferCount + staticBuffers; i++) {
+ entries.push({
+ binding: i,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type, hasDynamicOffset: false }
+ });
+ }
+
+ const descriptor = {
+ entries
+ };
+
+ t.expectValidationError(
+ () => {
+ t.device.createBindGroupLayout(descriptor);
+ },
+ extraDynamicBuffers > 0 || entries.length > perStageLimit
+ );
+});
+
+/**
+ * One bind group layout will be filled with kPerStageBindingLimit[...] of the type |type|.
+ * For each item in the array returned here, a case will be generated which tests a pipeline
+ * layout with one extra bind group layout with one extra binding. That extra binding will have:
+ *
+ * - If extraTypeSame, any of the binding types which counts toward the same limit as |type|.
+ * (i.e. 'storage-buffer' <-> 'readonly-storage-buffer').
+ * - Otherwise, an arbitrary other type.
+ */
+function* pickExtraBindingTypesForPerStage(entry, extraTypeSame) {
+ if (extraTypeSame) {
+ const info = bindingTypeInfo(entry);
+ for (const extra of allBindingEntries(false)) {
+ const extraInfo = bindingTypeInfo(extra);
+ if (info.perStageLimitClass.class === extraInfo.perStageLimitClass.class) {
+ yield extra;
+ }
+ }
+ } else {
+ yield entry.sampler ? { texture: {} } : { sampler: {} };
+ }
+}
+
+const kMaxResourcesCases = kUnitCaseParamsBuilder.
+combine('maxedEntry', allBindingEntries(false)).
+beginSubcases().
+combine('maxedVisibility', kShaderStages).
+filter((p) => (bindingTypeInfo(p.maxedEntry).validStages & p.maxedVisibility) !== 0).
+expand('extraEntry', (p) => [
+...pickExtraBindingTypesForPerStage(p.maxedEntry, true),
+...pickExtraBindingTypesForPerStage(p.maxedEntry, false)]
+).
+combine('extraVisibility', kShaderStages).
+filter((p) => (bindingTypeInfo(p.extraEntry).validStages & p.extraVisibility) !== 0);
+
+// Should never fail unless limitInfo.maxBindingsPerBindGroup.default is exceeded, because the validation for
+// resources-of-type-per-stage is in pipeline layout creation.
+g.test('max_resources_per_stage,in_bind_group_layout').
+desc(
+ `
+ Test that the maximum number of bindings of a given type per-stage cannot be exceeded in a
+ single bind group layout.
+ - Test each binding type.
+ - Test that creation of a bind group layout using the maximum number of bindings works.
+ - Test that creation of a bind group layout using the maximum number of bindings + 1 fails.
+ - TODO(#230): Update to enforce per-stage and per-pipeline-layout limits on BGLs as well.`
+).
+params(kMaxResourcesCases).
+fn((t) => {
+ const { maxedEntry, extraEntry, maxedVisibility, extraVisibility } = t.params;
+ const maxedTypeInfo = bindingTypeInfo(maxedEntry);
+ const maxedCount = t.getDefaultLimit(maxedTypeInfo.perStageLimitClass.maxLimit);
+ const extraTypeInfo = bindingTypeInfo(extraEntry);
+
+ const maxResourceBindings = [];
+ for (let i = 0; i < maxedCount; i++) {
+ maxResourceBindings.push({
+ binding: i,
+ visibility: maxedVisibility,
+ ...maxedEntry
+ });
+ }
+
+ const goodDescriptor = { entries: maxResourceBindings };
+
+ // Control
+ t.device.createBindGroupLayout(goodDescriptor);
+
+ // Add an entry counting towards the same limit. It should produce a validation error.
+ const newDescriptor = clone(goodDescriptor);
+ newDescriptor.entries.push({
+ binding: maxedCount,
+ visibility: extraVisibility,
+ ...extraEntry
+ });
+
+ const newBindingCountsTowardSamePerStageLimit =
+ (maxedVisibility & extraVisibility) !== 0 &&
+ maxedTypeInfo.perStageLimitClass.class === extraTypeInfo.perStageLimitClass.class;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout(newDescriptor);
+ }, newBindingCountsTowardSamePerStageLimit);
+});
+
+// One pipeline layout can have a maximum number of each type of binding *per stage* (which is
+// different for each type). Test that the max works, then add one more binding of same-or-different
+// type and same-or-different visibility.
+g.test('max_resources_per_stage,in_pipeline_layout').
+desc(
+ `
+ Test that the maximum number of bindings of a given type per-stage cannot be exceeded across
+ multiple bind group layouts when creating a pipeline layout.
+ - Test each binding type.
+ - Test that creation of a pipeline using the maximum number of bindings works.
+ - Test that creation of a pipeline using the maximum number of bindings + 1 fails.
+ `
+).
+params(kMaxResourcesCases).
+fn((t) => {
+ const { maxedEntry, extraEntry, maxedVisibility, extraVisibility } = t.params;
+ const maxedTypeInfo = bindingTypeInfo(maxedEntry);
+ const maxedCount = t.getDefaultLimit(maxedTypeInfo.perStageLimitClass.maxLimit);
+ const extraTypeInfo = bindingTypeInfo(extraEntry);
+
+ const maxResourceBindings = [];
+ for (let i = 0; i < maxedCount; i++) {
+ maxResourceBindings.push({
+ binding: i,
+ visibility: maxedVisibility,
+ ...maxedEntry
+ });
+ }
+
+ const goodLayout = t.device.createBindGroupLayout({ entries: maxResourceBindings });
+
+ // Control
+ t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout] });
+
+ const extraLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: extraVisibility,
+ ...extraEntry
+ }]
+
+ });
+
+ // Some binding types use the same limit, e.g. 'storage-buffer' and 'readonly-storage-buffer'.
+ const newBindingCountsTowardSamePerStageLimit =
+ (maxedVisibility & extraVisibility) !== 0 &&
+ maxedTypeInfo.perStageLimitClass.class === extraTypeInfo.perStageLimitClass.class;
+
+ t.expectValidationError(() => {
+ t.device.createPipelineLayout({ bindGroupLayouts: [goodLayout, extraLayout] });
+ }, newBindingCountsTowardSamePerStageLimit);
+});
+
+g.test('storage_texture,layout_dimension').
+desc(
+ `
+ Test that viewDimension is not cube or cube-array if storageTextureLayout is not undefined.
+ `
+).
+params((u) =>
+u //
+.combine('viewDimension', [undefined, ...kTextureViewDimensions])
+).
+fn((t) => {
+ const { viewDimension } = t.params;
+
+ const success = viewDimension !== 'cube' && viewDimension !== `cube-array`;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: { format: 'rgba8unorm', viewDimension }
+ }]
+
+ });
+ }, !success);
+});
+
+g.test('storage_texture,formats').
+desc(
+ `
+ Test that a validation error is generated if the format doesn't support the storage usage.
+ `
+).
+params((u) => u.combine('format', kAllTextureFormats)).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ t.expectValidationError(
+ () => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: { format }
+ }]
+
+ });
+ },
+ !info.color?.storage
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createPipelineLayout.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createPipelineLayout.spec.js
new file mode 100644
index 0000000000..3d53eb703b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createPipelineLayout.spec.js
@@ -0,0 +1,164 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+createPipelineLayout validation tests.
+
+TODO: review existing tests, write descriptions, and make sure tests are complete.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { bufferBindingTypeInfo, kBufferBindingTypes } from '../../capability_info.js';
+
+import { ValidationTest } from './validation_test.js';
+
+function clone(descriptor) {
+ return JSON.parse(JSON.stringify(descriptor));
+}
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('number_of_dynamic_buffers_exceeds_the_maximum_value').
+desc(
+ `
+ Test that creating a pipeline layout fails with a validation error if the number of dynamic
+ buffers exceeds the maximum value in the pipeline layout.
+ - Test that creation of a pipeline using the maximum number of dynamic buffers added a dynamic
+ buffer fails.
+
+ TODO(#230): Update to enforce per-stage and per-pipeline-layout limits on BGLs as well.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('visibility', [0, 2, 4, 6]).
+combine('type', kBufferBindingTypes)
+).
+fn((t) => {
+ const { type, visibility } = t.params;
+ const info = bufferBindingTypeInfo({ type });
+ const { maxDynamicLimit } = info.perPipelineLimitClass;
+ const perStageLimit = t.getDefaultLimit(info.perStageLimitClass.maxLimit);
+ const maxDynamic = Math.min(
+ maxDynamicLimit ? t.getDefaultLimit(maxDynamicLimit) : 0,
+ perStageLimit
+ );
+
+ const maxDynamicBufferBindings = [];
+ for (let binding = 0; binding < maxDynamic; binding++) {
+ maxDynamicBufferBindings.push({
+ binding,
+ visibility,
+ buffer: { type, hasDynamicOffset: true }
+ });
+ }
+
+ const maxDynamicBufferBindGroupLayout = t.device.createBindGroupLayout({
+ entries: maxDynamicBufferBindings
+ });
+
+ const goodDescriptor = {
+ entries: [{ binding: 0, visibility, buffer: { type, hasDynamicOffset: false } }]
+ };
+
+ if (perStageLimit > maxDynamic) {
+ const goodPipelineLayoutDescriptor = {
+ bindGroupLayouts: [
+ maxDynamicBufferBindGroupLayout,
+ t.device.createBindGroupLayout(goodDescriptor)]
+
+ };
+
+ // Control case
+ t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
+ }
+
+ // Check dynamic buffers exceed maximum in pipeline layout.
+ const badDescriptor = clone(goodDescriptor);
+ badDescriptor.entries[0].buffer.hasDynamicOffset = true;
+
+ const badPipelineLayoutDescriptor = {
+ bindGroupLayouts: [
+ maxDynamicBufferBindGroupLayout,
+ t.device.createBindGroupLayout(badDescriptor)]
+
+ };
+
+ t.expectValidationError(() => {
+ t.device.createPipelineLayout(badPipelineLayoutDescriptor);
+ });
+});
+
+g.test('number_of_bind_group_layouts_exceeds_the_maximum_value').
+desc(
+ `
+ Test that creating a pipeline layout fails with a validation error if the number of bind group
+ layouts exceeds the maximum value in the pipeline layout.
+ - Test that creation of a pipeline using the maximum number of bind groups added a bind group
+ fails.
+ `
+).
+fn((t) => {
+ const bindGroupLayoutDescriptor = {
+ entries: []
+ };
+
+ // 4 is the maximum number of bind group layouts.
+ const maxBindGroupLayouts = [1, 2, 3, 4].map(() =>
+ t.device.createBindGroupLayout(bindGroupLayoutDescriptor)
+ );
+
+ const goodPipelineLayoutDescriptor = {
+ bindGroupLayouts: maxBindGroupLayouts
+ };
+
+ // Control case
+ t.device.createPipelineLayout(goodPipelineLayoutDescriptor);
+
+ // Check bind group layouts exceed maximum in pipeline layout.
+ const badPipelineLayoutDescriptor = {
+ bindGroupLayouts: [
+ ...maxBindGroupLayouts,
+ t.device.createBindGroupLayout(bindGroupLayoutDescriptor)]
+
+ };
+
+ t.expectValidationError(() => {
+ t.device.createPipelineLayout(badPipelineLayoutDescriptor);
+ });
+});
+
+g.test('bind_group_layouts,device_mismatch').
+desc(
+ `
+ Tests createPipelineLayout cannot be called with bind group layouts created from another device
+ Test with two layouts to make sure all layouts can be validated:
+ - layout0 and layout1 from same device
+ - layout0 and layout1 from different device
+ `
+).
+paramsSubcasesOnly([
+{ layout0Mismatched: false, layout1Mismatched: false }, // control case
+{ layout0Mismatched: true, layout1Mismatched: false },
+{ layout0Mismatched: false, layout1Mismatched: true }]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { layout0Mismatched, layout1Mismatched } = t.params;
+
+ const mismatched = layout0Mismatched || layout1Mismatched;
+
+ const bglDescriptor = {
+ entries: []
+ };
+
+ const layout0 = layout0Mismatched ?
+ t.mismatchedDevice.createBindGroupLayout(bglDescriptor) :
+ t.device.createBindGroupLayout(bglDescriptor);
+ const layout1 = layout1Mismatched ?
+ t.mismatchedDevice.createBindGroupLayout(bglDescriptor) :
+ t.device.createBindGroupLayout(bglDescriptor);
+
+ t.expectValidationError(() => {
+ t.device.createPipelineLayout({ bindGroupLayouts: [layout0, layout1] });
+ }, mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createSampler.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createSampler.spec.js
new file mode 100644
index 0000000000..7e198d00e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createSampler.spec.js
@@ -0,0 +1,68 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+createSampler validation tests.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+
+import { ValidationTest } from './validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('lodMinAndMaxClamp').
+desc('test different combinations of min and max clamp values').
+paramsSubcasesOnly((u) =>
+u //
+.combine('lodMinClamp', [-4e-30, -1, 0, 0.5, 1, 10, 4e30]).
+combine('lodMaxClamp', [-4e-30, -1, 0, 0.5, 1, 10, 4e30])
+).
+fn((t) => {
+ const shouldError =
+ t.params.lodMinClamp > t.params.lodMaxClamp ||
+ t.params.lodMinClamp < 0 ||
+ t.params.lodMaxClamp < 0;
+ t.expectValidationError(() => {
+ t.device.createSampler({
+ lodMinClamp: t.params.lodMinClamp,
+ lodMaxClamp: t.params.lodMaxClamp
+ });
+ }, shouldError);
+});
+
+g.test('maxAnisotropy').
+desc('test different maxAnisotropy values and combinations with min/mag/mipmapFilter').
+params((u) =>
+u //
+.beginSubcases().
+combineWithParams([
+...u.combine('maxAnisotropy', [-1, undefined, 0, 1, 2, 4, 7, 16, 32, 33, 1024]),
+{ minFilter: 'nearest' },
+{ magFilter: 'nearest' },
+{ mipmapFilter: 'nearest' }]
+)
+).
+fn((t) => {
+ const {
+ maxAnisotropy = 1,
+ minFilter = 'linear',
+ magFilter = 'linear',
+ mipmapFilter = 'linear'
+ } = t.params;
+
+
+
+
+
+
+ const shouldError =
+ maxAnisotropy < 1 ||
+ maxAnisotropy > 1 &&
+ !(minFilter === 'linear' && magFilter === 'linear' && mipmapFilter === 'linear');
+ t.expectValidationError(() => {
+ t.device.createSampler({
+ minFilter,
+ magFilter,
+ mipmapFilter,
+ maxAnisotropy
+ });
+ }, shouldError);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createTexture.spec.js
new file mode 100644
index 0000000000..d2ea68ef85
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createTexture.spec.js
@@ -0,0 +1,1130 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `createTexture validation tests.`;import { SkipTestCase } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert, makeValueTestVariant } from '../../../common/util/util.js';
+import { kTextureDimensions, kTextureUsages } from '../../capability_info.js';
+import { GPUConst } from '../../constants.js';
+import {
+ kTextureFormats,
+ kTextureFormatInfo,
+ kCompressedTextureFormats,
+ kUncompressedTextureFormats,
+ kRegularTextureFormats,
+ kFeaturesForFormats,
+ filterFormatsByFeature,
+ viewCompatible,
+ textureDimensionAndFormatCompatible } from
+'../../format_info.js';
+import { maxMipLevelCount } from '../../util/texture/base.js';
+
+import { ValidationTest } from './validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('zero_size_and_usage').
+desc(
+ `Test texture creation with zero or nonzero size of
+ width, height, depthOrArrayLayers and mipLevelCount, usage for every dimension, and
+ representative formats.
+ `
+).
+params((u) =>
+u.
+combine('dimension', [undefined, ...kTextureDimensions]).
+combine('format', [
+'rgba8unorm',
+'rgb10a2unorm',
+'bc1-rgba-unorm',
+'depth24plus-stencil8']
+).
+beginSubcases().
+combine('zeroArgument', [
+'none',
+'width',
+'height',
+'depthOrArrayLayers',
+'mipLevelCount',
+'usage']
+)
+// Filter out incompatible dimension type and format combinations.
+.filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, zeroArgument, format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const size = [info.blockWidth, info.blockHeight, 1];
+ let mipLevelCount = 1;
+ let usage = GPUTextureUsage.TEXTURE_BINDING;
+
+ switch (zeroArgument) {
+ case 'width':
+ size[0] = 0;
+ break;
+ case 'height':
+ size[1] = 0;
+ break;
+ case 'depthOrArrayLayers':
+ size[2] = 0;
+ break;
+ case 'mipLevelCount':
+ mipLevelCount = 0;
+ break;
+ case 'usage':
+ usage = 0;
+ break;
+ default:
+ break;
+ }
+
+ const descriptor = {
+ size,
+ mipLevelCount,
+ dimension,
+ format,
+ usage
+ };
+
+ const success = zeroArgument === 'none';
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('dimension_type_and_format_compatibility').
+desc(
+ `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)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = {
+ size: [info.blockWidth, info.blockHeight, 1],
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !textureDimensionAndFormatCompatible(dimension, format));
+});
+
+g.test('mipLevelCount,format').
+desc(
+ `Test texture creation with no mipmap chain, partial mipmap chain, full mipmap chain, out-of-bounds mipmap chain
+ for every format with different texture dimension types.`
+).
+params((u) =>
+u.
+combine('dimension', [undefined, ...kTextureDimensions]).
+combine('format', kTextureFormats).
+beginSubcases().
+combine('mipLevelCount', [1, 2, 3, 6, 7])
+// Filter out incompatible dimension type and format combinations.
+.filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+combine('largestDimension', [0, 1, 2]).
+unless(({ dimension, largestDimension }) => dimension === '1d' && largestDimension > 0)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format, mipLevelCount, largestDimension } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ // Compute dimensions such that the dimensions are in range [17, 32] and aligned with the
+ // format block size so that there will be exactly 6 mip levels.
+ const kTargetMipLevelCount = 5;
+ const kTargetLargeSize = (1 << kTargetMipLevelCount) - 1;
+ const largeSize = [
+ Math.floor(kTargetLargeSize / info.blockWidth) * info.blockWidth,
+ Math.floor(kTargetLargeSize / info.blockHeight) * info.blockHeight,
+ kTargetLargeSize];
+
+ assert(17 <= largeSize[0] && largeSize[0] <= 32);
+ assert(17 <= largeSize[1] && largeSize[1] <= 32);
+
+ // Note that compressed formats are not valid for 1D. They have already been filtered out for 1D
+ // in this test. So there is no dilemma about size.width equals 1 vs
+ // size.width % info.blockHeight equals 0 for 1D compressed formats.
+ const size = [info.blockWidth, info.blockHeight, 1];
+ size[largestDimension] = largeSize[largestDimension];
+
+ const descriptor = {
+ size,
+ mipLevelCount,
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const success = mipLevelCount <= maxMipLevelCount(descriptor);
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('mipLevelCount,bound_check').
+desc(
+ `Test mip level count bound check upon different texture size and different texture dimension types.
+ The cases below test: 1) there must be no mip levels after a 1 level (1D texture), or 1x1 level (2D texture), or 1x1x1 level (3D texture), 2) array layers are not mip-mapped, 3) power-of-two, non-power-of-two, and non-square sizes.`
+).
+params((u) =>
+u //
+.combine('format', ['rgba8unorm', 'bc1-rgba-unorm']).
+beginSubcases().
+combineWithParams([
+{ size: [32, 32] }, // Mip level sizes: 32x32, 16x16, 8x8, 4x4, 2x2, 1x1
+{ size: [31, 32] }, // Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1
+{ size: [28, 32] }, // Mip level sizes: 28x32, 14x16, 7x8, 3x4, 1x2, 1x1
+{ size: [32, 31] }, // Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1
+{ size: [32, 28] }, // Mip level sizes: 32x28, 16x14, 8x7, 4x3, 2x1, 1x1
+{ size: [31, 31] }, // Mip level sizes: 31x31, 15x15, 7x7, 3x3, 1x1
+{ size: [32], dimension: '1d' }, // Mip level sizes: 32, 16, 8, 4, 2, 1
+{ size: [31], dimension: '1d' }, // Mip level sizes: 31, 15, 7, 3, 1
+{ size: [32, 32, 32], dimension: '3d' }, // Mip level sizes: 32x32x32, 16x16x16, 8x8x8, 4x4x4, 2x2x2, 1x1x1
+{ size: [32, 31, 31], dimension: '3d' }, // Mip level sizes: 32x31x31, 16x15x15, 8x7x7, 4x3x3, 2x1x1, 1x1x1
+{ size: [31, 32, 31], dimension: '3d' }, // Mip level sizes: 31x32x31, 15x16x15, 7x8x7, 3x4x3, 1x2x1, 1x1x1
+{ size: [31, 31, 32], dimension: '3d' }, // Mip level sizes: 31x31x32, 15x15x16, 7x7x8, 3x3x4, 1x1x2, 1x1x1
+{ size: [31, 31, 31], dimension: '3d' }, // Mip level sizes: 31x31x31, 15x15x15, 7x7x7, 3x3x3, 1x1x1
+{ size: [32, 8] }, // Mip levels: 32x8, 16x4, 8x2, 4x1, 2x1, 1x1
+{ size: [32, 32, 64] }, // Mip levels: 32x32x64, 16x16x64, 8x8x64, 4x4x64, 2x2x64, 1x1x64
+{ size: [32, 32, 64], dimension: '3d' } // Mip levels: 32x32x64, 16x16x32, 8x8x16, 4x4x8, 2x2x4, 1x1x2, 1x1x1
+]).
+unless(
+ ({ format, size, dimension }) =>
+ format === 'bc1-rgba-unorm' && (
+ dimension === '1d' ||
+ dimension === '3d' ||
+ size[0] % kTextureFormatInfo[format].blockWidth !== 0 ||
+ size[1] % kTextureFormatInfo[format].blockHeight !== 0)
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, size, dimension } = t.params;
+
+ const descriptor = {
+ size,
+ mipLevelCount: 0,
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const mipLevelCount = maxMipLevelCount(descriptor);
+ descriptor.mipLevelCount = mipLevelCount;
+ t.device.createTexture(descriptor);
+
+ descriptor.mipLevelCount = mipLevelCount + 1;
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ });
+});
+
+g.test('mipLevelCount,bound_check,bigger_than_integer_bit_width').
+desc(`Test mip level count bound check when mipLevelCount is bigger than integer bit width`).
+fn((t) => {
+ const descriptor = {
+ size: [32, 32],
+ mipLevelCount: 100,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ });
+});
+
+g.test('sampleCount,various_sampleCount_with_all_formats').
+desc(
+ `Test texture creation with various (valid or invalid) sample count and all formats. Note that 1D and 3D textures can't support multisample.`
+).
+params((u) =>
+u.
+combine('dimension', [undefined, '2d']).
+combine('format', kTextureFormats).
+beginSubcases().
+combine('sampleCount', [0, 1, 2, 4, 8, 16, 32, 256])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, sampleCount, format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const usage =
+ sampleCount > 1 ?
+ GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT :
+ GPUTextureUsage.TEXTURE_BINDING;
+ const descriptor = {
+ size: [32 * info.blockWidth, 32 * info.blockHeight, 1],
+ sampleCount,
+ dimension,
+ format,
+ usage
+ };
+
+ const success = sampleCount === 1 || sampleCount === 4 && info.multisample && info.renderable;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('sampleCount,valid_sampleCount_with_other_parameter_varies').
+desc(
+ `Test texture creation with valid sample count when dimensions, arrayLayerCount, mipLevelCount,
+ format, and usage varies. Texture can be single sample (sampleCount is 1) or multi-sample
+ (sampleCount is 4). Multisample texture requires that
+ 1) its dimension is 2d or undefined,
+ 2) its format supports multisample,
+ 3) its mipLevelCount and arrayLayerCount are 1,
+ 4) its usage doesn't include STORAGE_BINDING,
+ 5) its usage includes RENDER_ATTACHMENT.`
+).
+params((u) =>
+u.
+combine('dimension', [undefined, ...kTextureDimensions]).
+combine('format', kTextureFormats).
+beginSubcases().
+combine('sampleCount', [1, 4]).
+combine('arrayLayerCount', [1, 2]).
+unless(
+ ({ dimension, arrayLayerCount }) =>
+ arrayLayerCount === 2 && dimension !== '2d' && dimension !== undefined
+).
+combine('mipLevelCount', [1, 2]).
+expand('usage', () => {
+ const usageSet = new Set();
+ for (const usage0 of kTextureUsages) {
+ for (const usage1 of kTextureUsages) {
+ usageSet.add(usage0 | usage1);
+ }
+ }
+ return usageSet;
+})
+// Filter out incompatible dimension type and format combinations.
+.filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+unless(({ usage, format, mipLevelCount, dimension }) => {
+ const info = kTextureFormatInfo[format];
+ return (
+ (usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && (
+ !info.colorRender || dimension !== '2d') ||
+ (usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.color?.storage ||
+ mipLevelCount !== 1 && dimension === '1d');
+
+})
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, sampleCount, format, mipLevelCount, arrayLayerCount, usage } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ const size =
+ dimension === '1d' ?
+ [32 * blockWidth, 1 * blockHeight, 1] :
+ dimension === '2d' || dimension === undefined ?
+ [32 * blockWidth, 32 * blockHeight, arrayLayerCount] :
+ [32 * blockWidth, 32 * blockHeight, 32];
+ const descriptor = {
+ size,
+ mipLevelCount,
+ sampleCount,
+ dimension,
+ format,
+ usage
+ };
+
+ const success =
+ sampleCount === 1 ||
+ sampleCount === 4 && (
+ dimension === '2d' || dimension === undefined) &&
+ kTextureFormatInfo[format].multisample &&
+ mipLevelCount === 1 &&
+ arrayLayerCount === 1 &&
+ (usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
+ (usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('sample_count,1d_2d_array_3d').
+desc(`Test that you can not create 1d, 2d_array, and 3d multisampled textures`).
+params((u) =>
+u.combineWithParams([
+{ dimension: '2d', size: [4, 4, 1], shouldError: false },
+{ dimension: '1d', size: [4, 1, 1], shouldError: true },
+{ dimension: '2d', size: [4, 4, 4], shouldError: true },
+{ dimension: '2d', size: [4, 4, 6], shouldError: true },
+{ dimension: '3d', size: [4, 4, 4], shouldError: true }]
+)
+).
+fn((t) => {
+ const { dimension, size, shouldError } = t.params;
+
+ t.expectValidationError(() => {
+ t.device.createTexture({
+ size,
+ dimension,
+ sampleCount: 4,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ }, shouldError);
+});
+
+g.test('texture_size,default_value_and_smallest_size,uncompressed_format').
+desc(
+ `Test default values for height and depthOrArrayLayers for every dimension type and every uncompressed format.
+ It also tests smallest size (lower bound) for every dimension type and every uncompressed format, while other texture_size tests are testing the upper bound.`
+).
+params((u) =>
+u.
+combine('dimension', [undefined, ...kTextureDimensions]).
+combine('format', kUncompressedTextureFormats).
+beginSubcases().
+combine('size', [[1], [1, 1], [1, 1, 1]])
+// Filter out incompatible dimension type and format combinations.
+.filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format, size } = t.params;
+
+ const descriptor = {
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ t.device.createTexture(descriptor);
+});
+
+g.test('texture_size,default_value_and_smallest_size,compressed_format').
+desc(
+ `Test default values for height and depthOrArrayLayers for every dimension type and every compressed format.
+ It also tests smallest size (lower bound) for every dimension type and every compressed format, while other texture_size tests are testing the upper bound.`
+).
+params((u) =>
+u
+// Compressed formats are invalid for 1D and 3D.
+.combine('dimension', [undefined, '2d']).
+combine('format', kCompressedTextureFormats).
+beginSubcases().
+expandWithParams((p) => {
+ const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
+ return [
+ { size: [1], _success: false },
+ { size: [blockWidth], _success: false },
+ { size: [1, 1], _success: false },
+ { size: [blockWidth, blockHeight], _success: true },
+ { size: [1, 1, 1], _success: false },
+ { size: [blockWidth, blockHeight, 1], _success: true }];
+
+})
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format, size, _success } = t.params;
+
+ const descriptor = {
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !_success);
+});
+
+g.test('texture_size,1d_texture').
+desc(`Test texture size requirement for 1D texture`).
+params((u) =>
+u //
+// Compressed and depth-stencil textures are invalid for 1D.
+.combine('format', kRegularTextureFormats).
+beginSubcases().
+combine('widthVariant', [
+{ mult: 1, add: -1 },
+{ mult: 1, add: 0 },
+{ mult: 1, add: 1 }]
+).
+combine('height', [1, 2]).
+combine('depthOrArrayLayers', [1, 2])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, widthVariant, height, depthOrArrayLayers } = t.params;
+ const width = t.makeLimitVariant('maxTextureDimension1D', widthVariant);
+
+ const descriptor = {
+ size: [width, height, depthOrArrayLayers],
+ dimension: '1d',
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const success =
+ width <= t.device.limits.maxTextureDimension1D && height === 1 && depthOrArrayLayers === 1;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('texture_size,2d_texture,uncompressed_format').
+desc(`Test texture size requirement for 2D texture with uncompressed format.`).
+params((u) =>
+u.
+combine('dimension', [undefined, '2d']).
+combine('format', kUncompressedTextureFormats).
+combine(
+ 'sizeVariant',
+ [
+ // Test the bound of width
+ [{ mult: 1, add: -1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
+ [{ mult: 1, add: 0 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
+ [{ mult: 1, add: 1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
+ // Test the bound of height
+ [{ mult: 0, add: 1 }, { mult: 1, add: -1 }, { mult: 0, add: 1 }],
+ [{ mult: 0, add: 1 }, { mult: 1, add: 0 }, { mult: 0, add: 1 }],
+ [{ mult: 0, add: 1 }, { mult: 1, add: 1 }, { mult: 0, add: 1 }],
+ // Test the bound of array layers
+ [{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: -1 }],
+ [{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 0 }],
+ [{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 1 }]]
+
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format, sizeVariant } = t.params;
+ const size = [
+ t.device.limits.maxTextureDimension2D,
+ t.device.limits.maxTextureDimension2D,
+ t.device.limits.maxTextureArrayLayers].
+ map((limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx]));
+
+ const descriptor = {
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const success =
+ size[0] <= t.device.limits.maxTextureDimension2D &&
+ size[1] <= t.device.limits.maxTextureDimension2D &&
+ size[2] <= t.device.limits.maxTextureArrayLayers;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('texture_size,2d_texture,compressed_format').
+desc(`Test texture size requirement for 2D texture with compressed format.`).
+params((u) =>
+u.
+combine('dimension', [undefined, '2d']).
+combine('format', kCompressedTextureFormats).
+expand('sizeVariant', (p) => {
+ const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
+ return [
+ // Test the bound of width
+ [
+ { mult: 1, add: -1 },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: -blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: -blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: 0 },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: 0 },
+ { mult: 0, add: blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 0, add: 1 }],
+
+ // Test the bound of height
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: -1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: -blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 1, add: -blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: 0 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 1, add: 0 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: +1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: +blockWidth },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 1, add: +blockHeight },
+ { mult: 0, add: 1 }],
+
+ // Test the bound of array layers
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 1, add: +1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 1, add: +1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: +1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: +1 }]];
+
+
+})
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format, sizeVariant } = t.params;
+ const info = kTextureFormatInfo[format];
+ const size = [
+ t.device.limits.maxTextureDimension2D,
+ t.device.limits.maxTextureDimension2D,
+ t.device.limits.maxTextureArrayLayers].
+ map((limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx]));
+
+ const descriptor = {
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const success =
+ size[0] % info.blockWidth === 0 &&
+ size[1] % info.blockHeight === 0 &&
+ size[0] <= t.device.limits.maxTextureDimension2D &&
+ size[1] <= t.device.limits.maxTextureDimension2D &&
+ size[2] <= t.device.limits.maxTextureArrayLayers;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('texture_size,3d_texture,uncompressed_format').
+desc(
+ `Test texture size requirement for 3D texture with uncompressed format. Note that depth/stencil formats are invalid for 3D textures, so we only test regular formats.`
+).
+params((u) =>
+u //
+.combine('format', kRegularTextureFormats).
+beginSubcases().
+combine(
+ 'sizeVariant',
+ [
+ // Test the bound of width
+ [{ mult: 1, add: -1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
+ [{ mult: 1, add: 0 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
+ [{ mult: 1, add: +1 }, { mult: 0, add: 1 }, { mult: 0, add: 1 }],
+ // Test the bound of height
+ [{ mult: 0, add: 1 }, { mult: 1, add: -1 }, { mult: 0, add: 1 }],
+ [{ mult: 0, add: 1 }, { mult: 1, add: 0 }, { mult: 0, add: 1 }],
+ [{ mult: 0, add: 1 }, { mult: 1, add: +1 }, { mult: 0, add: 1 }],
+ // Test the bound of depth
+ [{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: -1 }],
+ [{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: 0 }],
+ [{ mult: 0, add: 1 }, { mult: 0, add: 1 }, { mult: 1, add: +1 }]]
+
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, sizeVariant } = t.params;
+ const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
+ const size = sizeVariant.map((variant) => t.makeLimitVariant('maxTextureDimension3D', variant));
+
+ const descriptor = {
+ size,
+ dimension: '3d',
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const success =
+ size[0] <= maxTextureDimension3D &&
+ size[1] <= maxTextureDimension3D &&
+ size[2] <= maxTextureDimension3D;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('texture_size,3d_texture,compressed_format').
+desc(`Test texture size requirement for 3D texture with compressed format.`).
+params((u) =>
+u //
+.combine('format', kCompressedTextureFormats).
+beginSubcases().
+expand('sizeVariant', (p) => {
+ const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
+ return [
+ // Test the bound of width
+ [
+ { mult: 1, add: -1 },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: -blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: -blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: 0 },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: 0 },
+ { mult: 0, add: blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: +1 },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: +blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 1, add: +blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 0, add: 1 }],
+
+ // Test the bound of height
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: -1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: -blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 1, add: -blockHeight },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: 0 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 1, add: 0 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: +1 },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 1, add: +blockWidth },
+ { mult: 0, add: 1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 1, add: +blockHeight },
+ { mult: 0, add: 1 }],
+
+ // Test the bound of depth
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: -1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: 0 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: 1 },
+ { mult: 1, add: +1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: 1 },
+ { mult: 1, add: +1 }],
+
+ [
+ { mult: 0, add: 1 },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: +1 }],
+
+ [
+ { mult: 0, add: blockWidth },
+ { mult: 0, add: blockHeight },
+ { mult: 1, add: +1 }]];
+
+
+})
+).
+beforeAllSubcases((t) => {
+ // Compressed formats are not supported in 3D in WebGPU v1 because they are complicated but not very useful for now.
+ throw new SkipTestCase('Compressed 3D texture is not supported');
+
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, sizeVariant } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
+ const size = sizeVariant.map((variant) => t.makeLimitVariant('maxTextureDimension3D', variant));
+
+ assert(
+ maxTextureDimension3D % info.blockWidth === 0 &&
+ maxTextureDimension3D % info.blockHeight === 0
+ );
+
+ const descriptor = {
+ size,
+ dimension: '3d',
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const success =
+ size[0] % info.blockWidth === 0 &&
+ size[1] % info.blockHeight === 0 &&
+ size[0] <= maxTextureDimension3D &&
+ size[1] <= maxTextureDimension3D &&
+ size[2] <= maxTextureDimension3D;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('texture_usage').
+desc(
+ `Test texture usage (single usage or combined usages) for every texture format and every dimension type`
+).
+params((u) =>
+u.
+combine('dimension', [undefined, ...kTextureDimensions]).
+combine('format', kTextureFormats).
+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).
+combine('usage1', kTextureUsages)
+// Filter out incompatible dimension type and format combinations.
+.filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { dimension, format, usage0, usage1 } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const size = [info.blockWidth, info.blockHeight, 1];
+ const usage = usage0 | usage1;
+ const descriptor = {
+ size,
+ dimension,
+ format,
+ usage
+ };
+
+ let success = true;
+ const appliedDimension = dimension ?? '2d';
+ // 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;
+
+ t.expectValidationError(() => {
+ t.device.createTexture(descriptor);
+ }, !success);
+});
+
+g.test('viewFormats').
+desc(
+ `Test creating a texture with viewFormats list for all {texture format}x{view format}. Only compatible view formats should be valid.`
+).
+params((u) =>
+u.
+combine('formatFeature', kFeaturesForFormats).
+combine('viewFormatFeature', kFeaturesForFormats).
+beginSubcases().
+expand('format', ({ formatFeature }) =>
+filterFormatsByFeature(formatFeature, kTextureFormats)
+).
+expand('viewFormat', ({ viewFormatFeature }) =>
+filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+)
+).
+beforeAllSubcases((t) => {
+ const { formatFeature, viewFormatFeature } = t.params;
+ t.selectDeviceOrSkipTestCase([formatFeature, viewFormatFeature]);
+}).
+fn((t) => {
+ const { format, viewFormat } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ t.skipIfTextureFormatNotSupported(format, viewFormat);
+
+ const compatible = viewCompatible(format, viewFormat);
+
+ // Test the viewFormat in the list.
+ t.expectValidationError(() => {
+ t.device.createTexture({
+ format,
+ size: [blockWidth, blockHeight],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ viewFormats: [viewFormat]
+ });
+ }, !compatible);
+
+ // Test the viewFormat and the texture format in the list.
+ t.expectValidationError(() => {
+ t.device.createTexture({
+ format,
+ size: [blockWidth, blockHeight],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ viewFormats: [viewFormat, format]
+ });
+ }, !compatible);
+
+ // Test the viewFormat multiple times in the list.
+ t.expectValidationError(() => {
+ t.device.createTexture({
+ format,
+ size: [blockWidth, blockHeight],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ viewFormats: [viewFormat, viewFormat]
+ });
+ }, !compatible);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createView.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createView.spec.js
new file mode 100644
index 0000000000..b001cb0b7a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/createView.spec.js
@@ -0,0 +1,340 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `createView validation tests.`;import { kUnitCaseParamsBuilder } from '../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { unreachable } from '../../../common/util/util.js';
+import {
+ kTextureAspects,
+ kTextureDimensions,
+ kTextureViewDimensions } from
+'../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kTextureFormats,
+ kFeaturesForFormats,
+ filterFormatsByFeature,
+ viewCompatible } from
+'../../format_info.js';
+import { kResourceStates } from '../../gpu_test.js';
+import {
+ getTextureDimensionFromView,
+ reifyTextureViewDescriptor,
+ viewDimensionsForTextureDimension } from
+'../../util/texture/base.js';
+import { reifyExtent3D } from '../../util/unions.js';
+
+import { ValidationTest } from './validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+const kLevels = 6;
+
+g.test('format').
+desc(
+ `Views must have the view format compatible with the base texture, for all {texture format}x{view format}.`
+).
+params((u) =>
+u.
+combine('textureFormatFeature', kFeaturesForFormats).
+combine('viewFormatFeature', kFeaturesForFormats).
+beginSubcases().
+expand('textureFormat', ({ textureFormatFeature }) =>
+filterFormatsByFeature(textureFormatFeature, kTextureFormats)
+).
+expand('viewFormat', ({ viewFormatFeature }) =>
+filterFormatsByFeature(viewFormatFeature, [undefined, ...kTextureFormats])
+).
+combine('useViewFormatList', [false, true])
+).
+beforeAllSubcases((t) => {
+ const { textureFormatFeature, viewFormatFeature } = t.params;
+ t.selectDeviceOrSkipTestCase([textureFormatFeature, viewFormatFeature]);
+}).
+fn((t) => {
+ const { textureFormat, viewFormat, useViewFormatList } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[textureFormat];
+
+ t.skipIfTextureFormatNotSupported(textureFormat, viewFormat);
+
+ const compatible = viewFormat === undefined || viewCompatible(textureFormat, viewFormat);
+
+ const texture = t.device.createTexture({
+ format: textureFormat,
+ size: [blockWidth, blockHeight],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+
+ // This is a test of createView, not createTexture. Don't pass viewFormats here that
+ // are not compatible, as that is tested in createTexture.spec.ts.
+ viewFormats:
+ useViewFormatList && compatible && viewFormat !== undefined ? [viewFormat] : undefined
+ });
+
+ // Successful if there is no view format, no reinterpretation was required, or the formats are compatible
+ // and is was specified in the viewFormats list.
+ const success =
+ viewFormat === undefined || viewFormat === textureFormat || compatible && useViewFormatList;
+ t.expectValidationError(() => {
+ texture.createView({ format: viewFormat });
+ }, !success);
+});
+
+g.test('dimension').
+desc(
+ `For all {texture dimension}, {view dimension}, test that they must be compatible:
+ - 1d -> 1d
+ - 2d -> 2d, 2d-array, cube, or cube-array
+ - 3d -> 3d`
+).
+params((u) =>
+u.
+combine('textureDimension', kTextureDimensions).
+combine('viewDimension', [...kTextureViewDimensions, undefined])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureViewDimensionNotSupported(t.params.viewDimension);
+}).
+fn((t) => {
+ const { textureDimension, viewDimension } = t.params;
+
+ const size = textureDimension === '1d' ? [4] : [4, 4, 6];
+ const textureDescriptor = {
+ format: 'rgba8unorm',
+ dimension: textureDimension,
+ size,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+ const texture = t.device.createTexture(textureDescriptor);
+
+ const view = { dimension: viewDimension };
+ const reified = reifyTextureViewDescriptor(textureDescriptor, view);
+
+ const success = getTextureDimensionFromView(reified.dimension) === textureDimension;
+ t.expectValidationError(() => {
+ texture.createView(view);
+ }, !success);
+});
+
+g.test('aspect').
+desc(
+ `For every {format}x{aspect}, test that the view aspect must exist in the format:
+ - "all" is allowed for any format
+ - "depth-only" is allowed only for depth and depth-stencil formats
+ - "stencil-only" is allowed only for stencil and depth-stencil formats`
+).
+params((u) =>
+u //
+.combine('format', kTextureFormats).
+combine('aspect', kTextureAspects)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(format);
+}).
+fn((t) => {
+ const { format, aspect } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const texture = t.device.createTexture({
+ format,
+ size: [info.blockWidth, info.blockHeight, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ const success =
+ aspect === 'all' ||
+ aspect === 'depth-only' && info.depth ||
+ aspect === 'stencil-only' && info.stencil;
+ t.expectValidationError(() => {
+ texture.createView({ aspect });
+ }, !success);
+});
+
+const kTextureAndViewDimensions = kUnitCaseParamsBuilder.
+combine('textureDimension', kTextureDimensions).
+expand('viewDimension', (p) => [
+undefined,
+...viewDimensionsForTextureDimension(p.textureDimension)]
+);
+
+function validateCreateViewLayersLevels(tex, view) {
+ const textureLevels = tex.mipLevelCount ?? 1;
+ const textureLayers = tex.dimension === '2d' ? reifyExtent3D(tex.size).depthOrArrayLayers : 1;
+ const reified = reifyTextureViewDescriptor(tex, view);
+
+ let success =
+ reified.mipLevelCount > 0 &&
+ reified.baseMipLevel < textureLevels &&
+ reified.baseMipLevel + reified.mipLevelCount <= textureLevels &&
+ reified.arrayLayerCount > 0 &&
+ reified.baseArrayLayer < textureLayers &&
+ reified.baseArrayLayer + reified.arrayLayerCount <= textureLayers;
+ if (reified.dimension === '1d' || reified.dimension === '2d' || reified.dimension === '3d') {
+ success &&= reified.arrayLayerCount === 1;
+ } else if (reified.dimension === 'cube') {
+ success &&= reified.arrayLayerCount === 6;
+ } else if (reified.dimension === 'cube-array') {
+ success &&= reified.arrayLayerCount % 6 === 0;
+ }
+ return success;
+}
+
+g.test('array_layers').
+desc(
+ `For each texture dimension {1d,2d,3d}, for each possible view dimension for that texture
+ dimension (or undefined, which defaults to the texture dimension), test validation of layer
+ counts:
+ - 1d, 2d, and 3d must have exactly 1 layer
+ - 2d-array must have 1 or more layers
+ - cube must have 6 layers
+ - cube-array must have a positive multiple of 6 layers
+ - Defaulting of baseArrayLayer and arrayLayerCount
+ - baseArrayLayer+arrayLayerCount must be within the texture`
+).
+params(
+ kTextureAndViewDimensions.
+ beginSubcases().
+ expand('textureLayers', ({ textureDimension: d }) => d === '2d' ? [1, 6, 18] : [1]).
+ combine('textureLevels', [1, kLevels]).
+ unless((p) => p.textureDimension === '1d' && p.textureLevels !== 1).
+ expand(
+ 'baseArrayLayer',
+ ({ textureLayers: l }) => new Set([undefined, 0, 1, 5, 6, 7, l - 1, l, l + 1])
+ ).
+ expand('arrayLayerCount', function* ({ textureLayers: l, baseArrayLayer = 0 }) {
+ yield undefined;
+ for (const lastArrayLayer of new Set([0, 1, 5, 6, 7, l - 1, l, l + 1])) {
+ if (baseArrayLayer <= lastArrayLayer) yield lastArrayLayer - baseArrayLayer;
+ }
+ })
+).
+fn((t) => {
+ const {
+ textureDimension,
+ viewDimension,
+ textureLayers,
+ textureLevels,
+ baseArrayLayer,
+ arrayLayerCount
+ } = t.params;
+
+ t.skipIfTextureViewDimensionNotSupported(viewDimension);
+
+ const kWidth = 1 << kLevels - 1; // 32
+ const textureDescriptor = {
+ format: 'rgba8unorm',
+ dimension: textureDimension,
+ size:
+ textureDimension === '1d' ?
+ [kWidth] :
+ textureDimension === '2d' ?
+ [kWidth, kWidth, textureLayers] :
+ textureDimension === '3d' ?
+ [kWidth, kWidth, kWidth] :
+ unreachable(),
+ mipLevelCount: textureLevels,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const viewDescriptor = { dimension: viewDimension, baseArrayLayer, arrayLayerCount };
+ const success = validateCreateViewLayersLevels(textureDescriptor, viewDescriptor);
+
+ const texture = t.device.createTexture(textureDescriptor);
+ t.expectValidationError(() => {
+ texture.createView(viewDescriptor);
+ }, !success);
+});
+
+g.test('mip_levels').
+desc(
+ `Views must have at least one level, and must be within the level of the base texture.
+
+ - mipLevelCount=0 at various baseMipLevel values
+ - Cases where baseMipLevel+mipLevelCount goes past the end of the texture
+ - Cases with baseMipLevel or mipLevelCount undefined (compares against reference defaulting impl)
+ `
+).
+params(
+ kTextureAndViewDimensions.
+ beginSubcases().
+ combine('textureLevels', [1, kLevels - 2, kLevels]).
+ unless((p) => p.textureDimension === '1d' && p.textureLevels !== 1).
+ expand(
+ 'baseMipLevel',
+ ({ textureLevels: l }) => new Set([undefined, 0, 1, 5, 6, 7, l - 1, l, l + 1])
+ ).
+ expand('mipLevelCount', function* ({ textureLevels: l, baseMipLevel = 0 }) {
+ yield undefined;
+ for (const lastMipLevel of new Set([0, 1, 5, 6, 7, l - 1, l, l + 1])) {
+ if (baseMipLevel <= lastMipLevel) yield lastMipLevel - baseMipLevel;
+ }
+ })
+).
+fn((t) => {
+ const { textureDimension, viewDimension, textureLevels, baseMipLevel, mipLevelCount } =
+ t.params;
+
+ t.skipIfTextureViewDimensionNotSupported(viewDimension);
+
+ const textureDescriptor = {
+ format: 'rgba8unorm',
+ dimension: textureDimension,
+ size:
+ textureDimension === '1d' ? [32] : textureDimension === '3d' ? [32, 32, 32] : [32, 32, 18],
+ mipLevelCount: textureLevels,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+
+ const viewDescriptor = { dimension: viewDimension, baseMipLevel, mipLevelCount };
+ const success = validateCreateViewLayersLevels(textureDescriptor, viewDescriptor);
+
+ const texture = t.device.createTexture(textureDescriptor);
+ t.debug(`${mipLevelCount} ${success}`);
+ t.expectValidationError(() => {
+ texture.createView(viewDescriptor);
+ }, !success);
+});
+
+g.test('cube_faces_square').
+desc(
+ `Test that the X/Y dimensions of cube and cube array textures must be square.
+ - {2d (control case), cube, cube-array}`
+).
+params((u) =>
+u //
+.combine('dimension', ['2d', 'cube', 'cube-array']).
+combine('size', [
+[4, 4, 6],
+[5, 5, 6],
+[4, 5, 6],
+[4, 8, 6],
+[8, 4, 6]]
+)
+).
+fn((t) => {
+ const { dimension, size } = t.params;
+
+ t.skipIfTextureViewDimensionNotSupported(dimension);
+
+ const texture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ const success = dimension === '2d' || size[0] === size[1];
+ t.expectValidationError(() => {
+ texture.createView({ dimension });
+ }, !success);
+});
+
+g.test('texture_state').
+desc(`createView should fail if the texture is invalid (but succeed if it is destroyed)`).
+paramsSubcasesOnly((u) => u.combine('state', kResourceStates)).
+fn((t) => {
+ const { state } = t.params;
+ const texture = t.createTextureWithState(state);
+
+ t.expectValidationError(() => {
+ texture.createView();
+ }, state === 'invalid');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/debugMarker.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/debugMarker.spec.js
new file mode 100644
index 0000000000..c042630d6b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/debugMarker.spec.js
@@ -0,0 +1,98 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test validation of pushDebugGroup, popDebugGroup, and insertDebugMarker.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+
+import { ValidationTest } from './validation_test.js';
+
+class F extends ValidationTest {
+ beginRenderPass(commandEncoder) {
+ const attachmentTexture = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ this.trackForCleanup(attachmentTexture);
+ return commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachmentTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('push_pop_call_count_unbalance,command_encoder').
+desc(
+ `
+ Test that a validation error is generated if {push,pop} debug group call count is not paired.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('pushCount', [1, 2, 3]).
+combine('popCount', [1, 2, 3])
+).
+fn((t) => {
+ const { pushCount, popCount } = t.params;
+
+ const encoder = t.device.createCommandEncoder();
+
+ for (let i = 0; i < pushCount; ++i) {
+ encoder.pushDebugGroup('EventStart');
+ }
+
+ encoder.insertDebugMarker('Marker');
+
+ for (let i = 0; i < popCount; ++i) {
+ encoder.popDebugGroup();
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, pushCount !== popCount);
+});
+
+g.test('push_pop_call_count_unbalance,render_compute_pass').
+desc(
+ `
+ Test that a validation error is generated if {push,pop} debug group call count is not paired in
+ ComputePassEncoder and RenderPassEncoder.
+ `
+).
+params((u) =>
+u //
+.combine('passType', ['compute', 'render']).
+beginSubcases().
+combine('pushCount', [1, 2, 3]).
+combine('popCount', [1, 2, 3])
+).
+fn((t) => {
+ const { passType, pushCount, popCount } = t.params;
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pass = passType === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder);
+
+ for (let i = 0; i < pushCount; ++i) {
+ pass.pushDebugGroup('EventStart');
+ }
+
+ pass.insertDebugMarker('Marker');
+
+ for (let i = 0; i < popCount; ++i) {
+ pass.popDebugGroup();
+ }
+
+ t.expectValidationError(() => {
+ pass.end();
+ encoder.finish();
+ }, pushCount !== popCount);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginComputePass.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginComputePass.spec.js
new file mode 100644
index 0000000000..6ba9849291
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginComputePass.spec.js
@@ -0,0 +1,147 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for validation in beginComputePass and GPUComputePassDescriptor as its optional descriptor.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kQueryTypes } from '../../../capability_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+class F extends ValidationTest {
+ tryComputePass(success, descriptor) {
+ const encoder = this.device.createCommandEncoder();
+ const computePass = encoder.beginComputePass(descriptor);
+ computePass.end();
+
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('timestampWrites,query_set_type').
+desc(
+ `
+ Test that all entries of the timestampWrites must have type 'timestamp'. If all query types are
+ not 'timestamp' in GPUComputePassDescriptor, a validation error should be generated.
+ `
+).
+params((u) =>
+u //
+.combine('queryType', kQueryTypes)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForQueryTypeOrSkipTestCase(['timestamp', t.params.queryType]);
+}).
+fn((t) => {
+ const { queryType } = t.params;
+
+ const isValid = queryType === 'timestamp';
+
+ const timestampWrites = {
+ querySet: t.device.createQuerySet({ type: queryType, count: 2 }),
+ beginningOfPassWriteIndex: 0,
+ endOfPassWriteIndex: 1
+ };
+
+ const descriptor = {
+ timestampWrites
+ };
+
+ t.tryComputePass(isValid, descriptor);
+});
+
+g.test('timestampWrites,invalid_query_set').
+desc(`Tests that timestampWrite that has an invalid query set generates a validation error.`).
+params((u) => u.combine('querySetState', ['valid', 'invalid'])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+}).
+fn((t) => {
+ const { querySetState } = t.params;
+
+ const querySet = t.createQuerySetWithState(querySetState, {
+ type: 'timestamp',
+ count: 1
+ });
+
+ const timestampWrites = {
+ querySet,
+ beginningOfPassWriteIndex: 0
+ };
+
+ const descriptor = {
+ timestampWrites
+ };
+
+ t.tryComputePass(querySetState === 'valid', descriptor);
+});
+
+g.test('timestampWrites,query_index').
+desc(
+ `Test that querySet.count should be greater than timestampWrite.queryIndex, and that the
+ query indexes are unique.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('beginningOfPassWriteIndex', [undefined, 0, 1, 2, 3]).
+combine('endOfPassWriteIndex', [undefined, 0, 1, 2, 3])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+}).
+fn((t) => {
+ const { beginningOfPassWriteIndex, endOfPassWriteIndex } = t.params;
+
+ const querySetCount = 2;
+
+ const timestampWrites = {
+ querySet: t.device.createQuerySet({ type: 'timestamp', count: querySetCount }),
+ beginningOfPassWriteIndex,
+ endOfPassWriteIndex
+ };
+
+ const isValid =
+ beginningOfPassWriteIndex !== endOfPassWriteIndex && (
+ beginningOfPassWriteIndex === undefined || beginningOfPassWriteIndex < querySetCount) && (
+ endOfPassWriteIndex === undefined || endOfPassWriteIndex < querySetCount);
+
+ const descriptor = {
+ timestampWrites
+ };
+
+ t.tryComputePass(isValid, descriptor);
+});
+
+g.test('timestamp_query_set,device_mismatch').
+desc(
+ `
+ Tests beginComputePass cannot be called with a timestamp query set created from another device.
+ `
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ t.selectMismatchedDeviceOrSkipTestCase('timestamp-query');
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const timestampQuerySet = sourceDevice.createQuerySet({
+ type: 'timestamp',
+ count: 1
+ });
+
+ const timestampWrites = {
+ querySet: timestampQuerySet,
+ beginningOfPassWriteIndex: 0
+ };
+
+ const descriptor = {
+ timestampWrites
+ };
+
+ t.tryComputePass(!mismatched, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginRenderPass.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginRenderPass.spec.js
new file mode 100644
index 0000000000..8003268d99
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/beginRenderPass.spec.js
@@ -0,0 +1,215 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Note: render pass 'occlusionQuerySet' validation is tested in queries/general.spec.ts
+
+TODO: Check that depth-stencil attachment views must encompass all aspects.
+
+TODO: check for duplication (render_pass/, etc.), plan, and implement.
+Note possibly a lot of this should be operation tests instead.
+Notes:
+> - color attachments {zero, one, multiple}
+> - many different formats (some are non-renderable)
+> - is a view on a texture with multiple mip levels or array layers
+> - two attachments use the same view, or views of {intersecting, disjoint} ranges
+> - {without, with} resolve target
+> - resolve format compatibility with multisampled format
+> - {all possible load ops, load color {in range, negative, too large}}
+> - all possible store ops
+> - depth/stencil attachment
+> - {unset, all possible formats}
+> - {all possible {depth, stencil} load ops, load values {in range, negative, too large}}
+> - all possible {depth, stencil} store ops
+> - depthReadOnly {t,f}, stencilReadOnly {t,f}
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('color_attachments,device_mismatch').
+desc(
+ `
+ Tests beginRenderPass cannot be called with color attachments whose texture view or resolve target is created from another device
+ The 'view' and 'resolveTarget' are:
+ - created from same device in ColorAttachment0 and ColorAttachment1
+ - created from different device in ColorAttachment0 and ColorAttachment1
+ - created from same device in ColorAttachment0, but from different device in ColorAttachment1
+ `
+).
+paramsSubcasesOnly([
+{
+ view0Mismatched: false,
+ target0Mismatched: false,
+ view1Mismatched: false,
+ target1Mismatched: false
+}, // control case
+{
+ view0Mismatched: false,
+ target0Mismatched: true,
+ view1Mismatched: false,
+ target1Mismatched: true
+},
+{
+ view0Mismatched: true,
+ target0Mismatched: false,
+ view1Mismatched: true,
+ target1Mismatched: false
+},
+{
+ view0Mismatched: false,
+ target0Mismatched: false,
+ view1Mismatched: false,
+ target1Mismatched: true
+}]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { view0Mismatched, target0Mismatched, view1Mismatched, target1Mismatched } = t.params;
+ const mismatched = view0Mismatched || target0Mismatched || view1Mismatched || target1Mismatched;
+
+ const view0Texture = view0Mismatched ?
+ t.getDeviceMismatchedRenderTexture(4) :
+ t.getRenderTexture(4);
+ const target0Texture = target0Mismatched ?
+ t.getDeviceMismatchedRenderTexture() :
+ t.getRenderTexture();
+ const view1Texture = view1Mismatched ?
+ t.getDeviceMismatchedRenderTexture(4) :
+ t.getRenderTexture(4);
+ const target1Texture = target1Mismatched ?
+ t.getDeviceMismatchedRenderTexture() :
+ t.getRenderTexture();
+
+ const encoder = t.createEncoder('non-pass');
+ const pass = encoder.encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: view0Texture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ resolveTarget: target0Texture.createView()
+ },
+ {
+ view: view1Texture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ resolveTarget: target1Texture.createView()
+ }]
+
+ });
+ pass.end();
+
+ encoder.validateFinish(!mismatched);
+});
+
+g.test('depth_stencil_attachment,device_mismatch').
+desc(
+ 'Tests beginRenderPass cannot be called with a depth stencil attachment whose texture view is created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+
+ const descriptor = {
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'depth24plus-stencil8',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ };
+
+ const depthStencilTexture = mismatched ?
+ t.getDeviceMismatchedTexture(descriptor) :
+ t.device.createTexture(descriptor);
+
+ const encoder = t.createEncoder('non-pass');
+ const pass = encoder.encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: depthStencilTexture.createView(),
+ depthClearValue: 0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilClearValue: 0,
+ stencilLoadOp: 'clear',
+ stencilStoreOp: 'store'
+ }
+ });
+ pass.end();
+
+ encoder.validateFinish(!mismatched);
+});
+
+g.test('occlusion_query_set,device_mismatch').
+desc(
+ 'Tests beginRenderPass cannot be called with an occlusion query set created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const occlusionQuerySet = sourceDevice.createQuerySet({
+ type: 'occlusion',
+ count: 1
+ });
+ t.trackForCleanup(occlusionQuerySet);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet });
+ encoder.validateFinish(!mismatched);
+});
+
+g.test('timestamp_query_set,device_mismatch').
+desc(
+ `
+ Tests beginRenderPass cannot be called with a timestamp query set created from another device.
+ `
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ t.selectMismatchedDeviceOrSkipTestCase('timestamp-query');
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const timestampQuerySet = sourceDevice.createQuerySet({
+ type: 'timestamp',
+ count: 1
+ });
+
+ const timestampWrites = {
+ querySet: timestampQuerySet,
+ beginningOfPassWriteIndex: 0
+ };
+
+ const colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ const pass = encoder.encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }],
+
+ timestampWrites
+ });
+ pass.end();
+
+ encoder.validateFinish(!mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/clearBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/clearBuffer.spec.js
new file mode 100644
index 0000000000..839a82741b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/clearBuffer.spec.js
@@ -0,0 +1,246 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+API validation tests for clearBuffer.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kBufferUsages } from '../../../../capability_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import { kMaxSafeMultipleOf8 } from '../../../../util/math.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ TestClearBuffer(options)
+
+
+
+
+ {
+ const { buffer, offset, size, isSuccess } = options;
+
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.clearBuffer(buffer, offset, size);
+
+ this.expectValidationError(() => {
+ commandEncoder.finish();
+ }, !isSuccess);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('buffer_state').
+desc(`Test that clearing an invalid or destroyed buffer fails.`).
+params((u) => u.combine('bufferState', kResourceStates)).
+fn((t) => {
+ const { bufferState } = t.params;
+
+ const buffer = t.createBufferWithState(bufferState, {
+ size: 8,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.clearBuffer(buffer, 0, 8);
+
+ if (bufferState === 'invalid') {
+ t.expectValidationError(() => {
+ commandEncoder.finish();
+ });
+ } else {
+ const cmd = commandEncoder.finish();
+ t.expectValidationError(() => {
+ t.device.queue.submit([cmd]);
+ }, bufferState === 'destroyed');
+ }
+});
+
+g.test('buffer,device_mismatch').
+desc(`Tests clearBuffer cannot be called with buffer created from another device.`).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+ const size = 8;
+
+ const buffer = sourceDevice.createBuffer({
+ size,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(buffer);
+
+ t.TestClearBuffer({
+ buffer,
+ offset: 0,
+ size,
+ isSuccess: !mismatched
+ });
+});
+
+g.test('default_args').
+desc(`Test that calling clearBuffer with a default offset and size is valid.`).
+paramsSubcasesOnly([
+{ offset: undefined, size: undefined },
+{ offset: 4, size: undefined },
+{ offset: undefined, size: 8 }]
+).
+fn((t) => {
+ const { offset, size } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestClearBuffer({
+ buffer,
+ offset,
+ size,
+ isSuccess: true
+ });
+});
+
+g.test('buffer_usage').
+desc(`Test that only buffers with COPY_DST usage are valid to use with copyBuffers.`).
+paramsSubcasesOnly((u) =>
+u //
+.combine('usage', kBufferUsages)
+).
+fn((t) => {
+ const { usage } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage
+ });
+
+ t.TestClearBuffer({
+ buffer,
+ offset: 0,
+ size: 16,
+ isSuccess: usage === GPUBufferUsage.COPY_DST
+ });
+});
+
+g.test('size_alignment').
+desc(
+ `
+ Test that the clear size must be 4 byte aligned.
+ - Test size is not a multiple of 4.
+ - Test size is 0.
+ - Test size overflows the buffer size.
+ - Test size is omitted.
+ `
+).
+paramsSubcasesOnly([
+{ size: 0, _isSuccess: true },
+{ size: 2, _isSuccess: false },
+{ size: 4, _isSuccess: true },
+{ size: 5, _isSuccess: false },
+{ size: 8, _isSuccess: true },
+{ size: 20, _isSuccess: false },
+{ size: undefined, _isSuccess: true }]
+).
+fn((t) => {
+ const { size, _isSuccess: isSuccess } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestClearBuffer({
+ buffer,
+ offset: 0,
+ size,
+ isSuccess
+ });
+});
+
+g.test('offset_alignment').
+desc(
+ `
+ Test that the clear offsets must be 4 byte aligned.
+ - Test offset is not a multiple of 4.
+ - Test offset is larger than the buffer size.
+ - Test offset is omitted.
+ `
+).
+paramsSubcasesOnly([
+{ offset: 0, _isSuccess: true },
+{ offset: 2, _isSuccess: false },
+{ offset: 4, _isSuccess: true },
+{ offset: 5, _isSuccess: false },
+{ offset: 8, _isSuccess: true },
+{ offset: 20, _isSuccess: false },
+{ offset: undefined, _isSuccess: true }]
+).
+fn((t) => {
+ const { offset, _isSuccess: isSuccess } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestClearBuffer({
+ buffer,
+ offset,
+ size: 8,
+ isSuccess
+ });
+});
+
+g.test('overflow').
+desc(`Test that clears which may cause arithmetic overflows are invalid.`).
+paramsSubcasesOnly([
+{ offset: 0, size: kMaxSafeMultipleOf8 },
+{ offset: 16, size: kMaxSafeMultipleOf8 },
+{ offset: kMaxSafeMultipleOf8, size: 16 },
+{ offset: kMaxSafeMultipleOf8, size: kMaxSafeMultipleOf8 }]
+).
+fn((t) => {
+ const { offset, size } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestClearBuffer({
+ buffer,
+ offset,
+ size,
+ isSuccess: false
+ });
+});
+
+g.test('out_of_bounds').
+desc(`Test that clears which exceed the buffer bounds are invalid.`).
+paramsSubcasesOnly([
+{ offset: 0, size: 32, _isSuccess: true },
+{ offset: 0, size: 36 },
+{ offset: 32, size: 0, _isSuccess: true },
+{ offset: 32, size: 4 },
+{ offset: 36, size: 4 },
+{ offset: 36, size: 0 },
+{ offset: 20, size: 16 },
+{ offset: 20, size: 12, _isSuccess: true }]
+).
+fn((t) => {
+ const { offset, size, _isSuccess = false } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 32,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestClearBuffer({
+ buffer,
+ offset,
+ size,
+ isSuccess: _isSuccess
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/compute_pass.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/compute_pass.spec.js
new file mode 100644
index 0000000000..d810e8757a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/compute_pass.spec.js
@@ -0,0 +1,259 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+API validation test for compute pass
+
+Does **not** test usage scopes (resource_usages/) or programmable pass stuff (programmable_pass).
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { makeValueTestVariant } from '../../../../../common/util/util.js';
+import { kBufferUsages } from '../../../../capability_info.js';
+import { GPUConst } from '../../../../constants.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ createComputePipeline(state) {
+ if (state === 'valid') {
+ return this.createNoOpComputePipeline();
+ }
+
+ return this.createErrorComputePipeline();
+ }
+
+ createIndirectBuffer(state, data) {
+ const descriptor = {
+ size: data.byteLength,
+ usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST
+ };
+
+ if (state === 'invalid') {
+ descriptor.usage = 0xffff; // Invalid GPUBufferUsage
+ }
+
+ this.device.pushErrorScope('validation');
+ const buffer = this.device.createBuffer(descriptor);
+ void this.device.popErrorScope();
+
+ if (state === 'valid') {
+ this.queue.writeBuffer(buffer, 0, data);
+ }
+
+ if (state === 'destroyed') {
+ buffer.destroy();
+ }
+
+ return buffer;
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('set_pipeline').
+desc(
+ `
+setPipeline should generate an error iff using an 'invalid' pipeline.
+`
+).
+params((u) => u.beginSubcases().combine('state', ['valid', 'invalid'])).
+fn((t) => {
+ const { state } = t.params;
+ const pipeline = t.createComputePipeline(state);
+
+ const { encoder, validateFinishAndSubmitGivenState } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ validateFinishAndSubmitGivenState(state);
+});
+
+g.test('pipeline,device_mismatch').
+desc('Tests setPipeline cannot be called with a compute pipeline created from another device').
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const pipeline = sourceDevice.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: sourceDevice.createShaderModule({
+ code: '@compute @workgroup_size(1) fn main() {}'
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const { encoder, validateFinish } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ validateFinish(!mismatched);
+});
+
+g.test('dispatch_sizes').
+desc(
+ `Test 'direct' and 'indirect' dispatch with various sizes.
+
+ Only direct dispatches can produce validation errors.
+ Workgroup sizes:
+ - valid: { zero, one, just under limit }
+ - invalid: { just over limit, way over limit }
+
+ TODO: Verify that the invalid cases don't execute any invocations at all.
+`
+).
+params((u) =>
+u.
+combine('dispatchType', ['direct', 'indirect']).
+combine('largeDimValueVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: 0 },
+{ mult: 1, add: 1 },
+{ mult: 0, add: 0x7fff_ffff },
+{ mult: 0, add: 0xffff_ffff }]
+).
+beginSubcases().
+combine('largeDimIndex', [0, 1, 2]).
+combine('smallDimValue', [0, 1])
+).
+fn((t) => {
+ const { dispatchType, largeDimIndex, smallDimValue, largeDimValueVariant } = t.params;
+ const maxDispatch = t.device.limits.maxComputeWorkgroupsPerDimension;
+ const largeDimValue = makeValueTestVariant(maxDispatch, largeDimValueVariant);
+
+ const pipeline = t.createNoOpComputePipeline();
+
+ const workSizes = [smallDimValue, smallDimValue, smallDimValue];
+ workSizes[largeDimIndex] = largeDimValue;
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ if (dispatchType === 'direct') {
+ const [x, y, z] = workSizes;
+ encoder.dispatchWorkgroups(x, y, z);
+ } else if (dispatchType === 'indirect') {
+ encoder.dispatchWorkgroupsIndirect(
+ t.createIndirectBuffer('valid', new Uint32Array(workSizes)),
+ 0
+ );
+ }
+
+ const shouldError =
+ dispatchType === 'direct' && (
+ workSizes[0] > maxDispatch || workSizes[1] > maxDispatch || workSizes[2] > maxDispatch);
+
+ validateFinishAndSubmit(!shouldError, true);
+});
+
+const kBufferData = new Uint32Array(6).fill(1);
+g.test('indirect_dispatch_buffer_state').
+desc(
+ `
+Test dispatchWorkgroupsIndirect validation by submitting various dispatches with a no-op pipeline
+and an indirectBuffer with 6 elements.
+- indirectBuffer: {'valid', 'invalid', 'destroyed'}
+- indirectOffset:
+ - valid, within the buffer: {beginning, middle, end} of the buffer
+ - invalid, non-multiple of 4
+ - invalid, the last element is outside the buffer
+`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('state', kResourceStates).
+combine('offset', [
+// valid (for 'valid' buffers)
+0,
+Uint32Array.BYTES_PER_ELEMENT,
+kBufferData.byteLength - 3 * Uint32Array.BYTES_PER_ELEMENT,
+// invalid, non-multiple of 4 offset
+1,
+// invalid, last element outside buffer
+kBufferData.byteLength - 2 * Uint32Array.BYTES_PER_ELEMENT]
+)
+).
+fn((t) => {
+ const { state, offset } = t.params;
+ const pipeline = t.createNoOpComputePipeline();
+ const buffer = t.createIndirectBuffer(state, kBufferData);
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ encoder.dispatchWorkgroupsIndirect(buffer, offset);
+
+ const finishShouldError =
+ state === 'invalid' ||
+ offset % 4 !== 0 ||
+ offset + 3 * Uint32Array.BYTES_PER_ELEMENT > kBufferData.byteLength;
+ validateFinishAndSubmit(!finishShouldError, state !== 'destroyed');
+});
+
+g.test('indirect_dispatch_buffer,device_mismatch').
+desc(
+ `Tests dispatchWorkgroupsIndirect cannot be called with an indirect buffer created from another device`
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+
+ const pipeline = t.createNoOpComputePipeline();
+
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const buffer = sourceDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.INDIRECT
+ });
+ t.trackForCleanup(buffer);
+
+ const { encoder, validateFinish } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ encoder.dispatchWorkgroupsIndirect(buffer, 0);
+ validateFinish(!mismatched);
+});
+
+g.test('indirect_dispatch_buffer,usage').
+desc(
+ `
+ Tests dispatchWorkgroupsIndirect generates a validation error if the buffer usage does not
+ contain INDIRECT usage.
+ `
+).
+paramsSubcasesOnly((u) =>
+u
+// If bufferUsage0 and bufferUsage1 are the same, the usage being test is a single usage.
+// Otherwise, it's a combined usage.
+.combine('bufferUsage0', kBufferUsages).
+combine('bufferUsage1', kBufferUsages).
+unless(
+ ({ bufferUsage0, bufferUsage1 }) =>
+ ((bufferUsage0 | bufferUsage1) & (
+ GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE)) !==
+ 0
+)
+).
+fn((t) => {
+ const { bufferUsage0, bufferUsage1 } = t.params;
+
+ const bufferUsage = bufferUsage0 | bufferUsage1;
+
+ const layout = t.device.createPipelineLayout({ bindGroupLayouts: [] });
+ const pipeline = t.createNoOpComputePipeline(layout);
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: bufferUsage
+ });
+ t.trackForCleanup(buffer);
+
+ const success = (GPUBufferUsage.INDIRECT & bufferUsage) !== 0;
+
+ const { encoder, validateFinish } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+
+ encoder.dispatchWorkgroupsIndirect(buffer, 0);
+ validateFinish(success);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyBufferToBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyBufferToBuffer.spec.js
new file mode 100644
index 0000000000..a7aa2b5b79
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyBufferToBuffer.spec.js
@@ -0,0 +1,326 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyBufferToBuffer tests.
+
+Test Plan:
+* Buffer is valid/invalid
+ - the source buffer is invalid
+ - the destination buffer is invalid
+* Buffer usages
+ - the source buffer is created without GPUBufferUsage::COPY_SRC
+ - the destination buffer is created without GPUBufferUsage::COPY_DEST
+* CopySize
+ - copySize is not a multiple of 4
+ - copySize is 0
+* copy offsets
+ - sourceOffset is not a multiple of 4
+ - destinationOffset is not a multiple of 4
+* Arithmetic overflow
+ - (sourceOffset + copySize) is overflow
+ - (destinationOffset + copySize) is overflow
+* Out of bounds
+ - (sourceOffset + copySize) > size of source buffer
+ - (destinationOffset + copySize) > size of destination buffer
+* Source buffer and destination buffer are the same buffer
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kBufferUsages } from '../../../../capability_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import { kMaxSafeMultipleOf8 } from '../../../../util/math.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ TestCopyBufferToBuffer(options)
+
+
+
+
+
+
+ {
+ const { srcBuffer, srcOffset, dstBuffer, dstOffset, copySize, expectation } = options;
+
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyBufferToBuffer(srcBuffer, srcOffset, dstBuffer, dstOffset, copySize);
+
+ if (expectation === 'FinishError') {
+ this.expectValidationError(() => {
+ commandEncoder.finish();
+ });
+ } else {
+ const cmd = commandEncoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, expectation === 'SubmitError');
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('buffer_state').
+params((u) =>
+u //
+.combine('srcBufferState', kResourceStates).
+combine('dstBufferState', kResourceStates)
+).
+fn((t) => {
+ const { srcBufferState, dstBufferState } = t.params;
+ const srcBuffer = t.createBufferWithState(srcBufferState, {
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ const dstBuffer = t.createBufferWithState(dstBufferState, {
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const shouldFinishError = srcBufferState === 'invalid' || dstBufferState === 'invalid';
+ const shouldSubmitSuccess = srcBufferState === 'valid' && dstBufferState === 'valid';
+ const expectation = shouldSubmitSuccess ?
+ 'Success' :
+ shouldFinishError ?
+ 'FinishError' :
+ 'SubmitError';
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset: 0,
+ dstBuffer,
+ dstOffset: 0,
+ copySize: 8,
+ expectation
+ });
+});
+
+g.test('buffer,device_mismatch').
+desc(
+ 'Tests copyBufferToBuffer cannot be called with src buffer or dst buffer created from another device'
+).
+paramsSubcasesOnly([
+{ srcMismatched: false, dstMismatched: false }, // control case
+{ srcMismatched: true, dstMismatched: false },
+{ srcMismatched: false, dstMismatched: true }]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { srcMismatched, dstMismatched } = t.params;
+
+ const srcBufferDevice = srcMismatched ? t.mismatchedDevice : t.device;
+ const srcBuffer = srcBufferDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(srcBuffer);
+
+ const dstBufferDevice = dstMismatched ? t.mismatchedDevice : t.device;
+ const dstBuffer = dstBufferDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(dstBuffer);
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset: 0,
+ dstBuffer,
+ dstOffset: 0,
+ copySize: 8,
+ expectation: srcMismatched || dstMismatched ? 'FinishError' : 'Success'
+ });
+});
+
+g.test('buffer_usage').
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcUsage', kBufferUsages).
+combine('dstUsage', kBufferUsages)
+).
+fn((t) => {
+ const { srcUsage, dstUsage } = t.params;
+
+ const srcBuffer = t.device.createBuffer({
+ size: 16,
+ usage: srcUsage
+ });
+ const dstBuffer = t.device.createBuffer({
+ size: 16,
+ usage: dstUsage
+ });
+
+ const isSuccess = srcUsage === GPUBufferUsage.COPY_SRC && dstUsage === GPUBufferUsage.COPY_DST;
+ const expectation = isSuccess ? 'Success' : 'FinishError';
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset: 0,
+ dstBuffer,
+ dstOffset: 0,
+ copySize: 8,
+ expectation
+ });
+});
+
+g.test('copy_size_alignment').
+paramsSubcasesOnly([
+{ copySize: 0, _isSuccess: true },
+{ copySize: 2, _isSuccess: false },
+{ copySize: 4, _isSuccess: true },
+{ copySize: 5, _isSuccess: false },
+{ copySize: 8, _isSuccess: true }]
+).
+fn((t) => {
+ const { copySize, _isSuccess: isSuccess } = t.params;
+
+ const srcBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ const dstBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset: 0,
+ dstBuffer,
+ dstOffset: 0,
+ copySize,
+ expectation: isSuccess ? 'Success' : 'FinishError'
+ });
+});
+
+g.test('copy_offset_alignment').
+paramsSubcasesOnly([
+{ srcOffset: 0, dstOffset: 0, _isSuccess: true },
+{ srcOffset: 2, dstOffset: 0, _isSuccess: false },
+{ srcOffset: 4, dstOffset: 0, _isSuccess: true },
+{ srcOffset: 5, dstOffset: 0, _isSuccess: false },
+{ srcOffset: 8, dstOffset: 0, _isSuccess: true },
+{ srcOffset: 0, dstOffset: 2, _isSuccess: false },
+{ srcOffset: 0, dstOffset: 4, _isSuccess: true },
+{ srcOffset: 0, dstOffset: 5, _isSuccess: false },
+{ srcOffset: 0, dstOffset: 8, _isSuccess: true },
+{ srcOffset: 4, dstOffset: 4, _isSuccess: true }]
+).
+fn((t) => {
+ const { srcOffset, dstOffset, _isSuccess: isSuccess } = t.params;
+
+ const srcBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ const dstBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset,
+ dstBuffer,
+ dstOffset,
+ copySize: 8,
+ expectation: isSuccess ? 'Success' : 'FinishError'
+ });
+});
+
+g.test('copy_overflow').
+paramsSubcasesOnly([
+{ srcOffset: 0, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
+{ srcOffset: 16, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
+{ srcOffset: 0, dstOffset: 16, copySize: kMaxSafeMultipleOf8 },
+{ srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: 16 },
+{ srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: 16 },
+{ srcOffset: kMaxSafeMultipleOf8, dstOffset: 0, copySize: kMaxSafeMultipleOf8 },
+{ srcOffset: 0, dstOffset: kMaxSafeMultipleOf8, copySize: kMaxSafeMultipleOf8 },
+{
+ srcOffset: kMaxSafeMultipleOf8,
+ dstOffset: kMaxSafeMultipleOf8,
+ copySize: kMaxSafeMultipleOf8
+}]
+).
+fn((t) => {
+ const { srcOffset, dstOffset, copySize } = t.params;
+
+ const srcBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ const dstBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset,
+ dstBuffer,
+ dstOffset,
+ copySize,
+ expectation: 'FinishError'
+ });
+});
+
+g.test('copy_out_of_bounds').
+paramsSubcasesOnly([
+{ srcOffset: 0, dstOffset: 0, copySize: 32, _isSuccess: true },
+{ srcOffset: 0, dstOffset: 0, copySize: 36 },
+{ srcOffset: 36, dstOffset: 0, copySize: 4 },
+{ srcOffset: 0, dstOffset: 36, copySize: 4 },
+{ srcOffset: 36, dstOffset: 0, copySize: 0 },
+{ srcOffset: 0, dstOffset: 36, copySize: 0 },
+{ srcOffset: 20, dstOffset: 0, copySize: 16 },
+{ srcOffset: 20, dstOffset: 0, copySize: 12, _isSuccess: true },
+{ srcOffset: 0, dstOffset: 20, copySize: 16 },
+{ srcOffset: 0, dstOffset: 20, copySize: 12, _isSuccess: true }]
+).
+fn((t) => {
+ const { srcOffset, dstOffset, copySize, _isSuccess = false } = t.params;
+
+ const srcBuffer = t.device.createBuffer({
+ size: 32,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ const dstBuffer = t.device.createBuffer({
+ size: 32,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer,
+ srcOffset,
+ dstBuffer,
+ dstOffset,
+ copySize,
+ expectation: _isSuccess ? 'Success' : 'FinishError'
+ });
+});
+
+g.test('copy_within_same_buffer').
+paramsSubcasesOnly([
+{ srcOffset: 0, dstOffset: 8, copySize: 4 },
+{ srcOffset: 8, dstOffset: 0, copySize: 4 },
+{ srcOffset: 0, dstOffset: 4, copySize: 8 },
+{ srcOffset: 4, dstOffset: 0, copySize: 8 }]
+).
+fn((t) => {
+ const { srcOffset, dstOffset, copySize } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ t.TestCopyBufferToBuffer({
+ srcBuffer: buffer,
+ srcOffset,
+ dstBuffer: buffer,
+ dstOffset,
+ copySize,
+ expectation: 'FinishError'
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.js
new file mode 100644
index 0000000000..00bf4e83d8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.js
@@ -0,0 +1,874 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyTextureToTexture tests.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kTextureUsages, kTextureDimensions } from '../../../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kTextureFormats,
+ kCompressedTextureFormats,
+ kDepthStencilFormats,
+ kFeaturesForFormats,
+ filterFormatsByFeature,
+ textureDimensionAndFormatCompatible } from
+'../../../../format_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import { align, lcm } from '../../../../util/math.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ TestCopyTextureToTexture(
+ source,
+ destination,
+ copySize,
+ expectation)
+ {
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyTextureToTexture(source, destination, copySize);
+
+ if (expectation === 'FinishError') {
+ this.expectValidationError(() => {
+ commandEncoder.finish();
+ });
+ } else {
+ const cmd = commandEncoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, expectation === 'SubmitError');
+ }
+ }
+
+ GetPhysicalSubresourceSize(
+ dimension,
+ textureSize,
+ format,
+ mipLevel)
+ {
+ const virtualWidthAtLevel = Math.max(textureSize.width >> mipLevel, 1);
+ const virtualHeightAtLevel = Math.max(textureSize.height >> mipLevel, 1);
+ const physicalWidthAtLevel = align(virtualWidthAtLevel, kTextureFormatInfo[format].blockWidth);
+ const physicalHeightAtLevel = align(
+ virtualHeightAtLevel,
+ kTextureFormatInfo[format].blockHeight
+ );
+
+ switch (dimension) {
+ case '1d':
+ return { width: physicalWidthAtLevel, height: 1, depthOrArrayLayers: 1 };
+ case '2d':
+ return {
+ width: physicalWidthAtLevel,
+ height: physicalHeightAtLevel,
+ depthOrArrayLayers: textureSize.depthOrArrayLayers
+ };
+ case '3d':
+ return {
+ width: physicalWidthAtLevel,
+ height: physicalHeightAtLevel,
+ depthOrArrayLayers: Math.max(textureSize.depthOrArrayLayers >> mipLevel, 1)
+ };
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('copy_with_invalid_or_destroyed_texture').
+desc('Test copyTextureToTexture is an error when one of the textures is invalid or destroyed.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcState', kResourceStates).
+combine('dstState', kResourceStates)
+).
+fn((t) => {
+ const { srcState, dstState } = t.params;
+
+ const textureDesc = {
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ };
+
+ const srcTexture = t.createTextureWithState(srcState, textureDesc);
+ const dstTexture = t.createTextureWithState(dstState, textureDesc);
+
+ const isSubmitSuccess = srcState === 'valid' && dstState === 'valid';
+ const isFinishSuccess = srcState !== 'invalid' && dstState !== 'invalid';
+ const expectation = isFinishSuccess ?
+ isSubmitSuccess ?
+ 'Success' :
+ 'SubmitError' :
+ 'FinishError';
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ expectation
+ );
+});
+
+g.test('texture,device_mismatch').
+desc(
+ 'Tests copyTextureToTexture cannot be called with src texture or dst texture created from another device.'
+).
+paramsSubcasesOnly([
+{ srcMismatched: false, dstMismatched: false }, // control case
+{ srcMismatched: true, dstMismatched: false },
+{ srcMismatched: false, dstMismatched: true }]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { srcMismatched, dstMismatched } = t.params;
+
+ const size = { width: 4, height: 4, depthOrArrayLayers: 1 };
+ const format = 'rgba8unorm';
+
+ const srcTextureDevice = srcMismatched ? t.mismatchedDevice : t.device;
+ const srcTexture = srcTextureDevice.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ t.trackForCleanup(srcTexture);
+
+ const dstTextureDevice = dstMismatched ? t.mismatchedDevice : t.device;
+ const dstTexture = dstTextureDevice.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_DST
+ });
+ t.trackForCleanup(dstTexture);
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ srcMismatched || dstMismatched ? 'FinishError' : 'Success'
+ );
+});
+
+g.test('mipmap_level').
+desc(
+ `
+Test copyTextureToTexture must specify mipLevels that are in range.
+- for various dimensions
+- for various mip level count in the texture
+- for various copy target mip level (in range and not in range)
+`
+).
+params((u) =>
+u //
+.combine('dimension', kTextureDimensions).
+beginSubcases().
+combineWithParams([
+{ srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 0 },
+{ srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 1, dstCopyLevel: 0 },
+{ srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 1 },
+{ srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 0 },
+{ srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 2, dstCopyLevel: 0 },
+{ srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 3, dstCopyLevel: 0 },
+{ srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 2 },
+{ srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 3 }]
+).
+unless((p) => p.dimension === '1d' && (p.srcLevelCount !== 1 || p.dstLevelCount !== 1))
+).
+
+fn((t) => {
+ const { srcLevelCount, dstLevelCount, srcCopyLevel, dstCopyLevel, dimension } = t.params;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: 32, height: 1, depthOrArrayLayers: 1 },
+ dimension,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC,
+ mipLevelCount: srcLevelCount
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: 32, height: 1, depthOrArrayLayers: 1 },
+ dimension,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST,
+ mipLevelCount: dstLevelCount
+ });
+
+ const isSuccess = srcCopyLevel < srcLevelCount && dstCopyLevel < dstLevelCount;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, mipLevel: srcCopyLevel },
+ { texture: dstTexture, mipLevel: dstCopyLevel },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('texture_usage').
+desc(
+ `
+Test that copyTextureToTexture source/destination need COPY_SRC/COPY_DST usages.
+- for all possible source texture usages
+- for all possible destination texture usages
+`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcUsage', kTextureUsages).
+combine('dstUsage', kTextureUsages)
+).
+fn((t) => {
+ const { srcUsage, dstUsage } = t.params;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: srcUsage
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: dstUsage
+ });
+
+ const isSuccess =
+ srcUsage === GPUTextureUsage.COPY_SRC && dstUsage === GPUTextureUsage.COPY_DST;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('sample_count').
+desc(
+ `
+Test that textures in copyTextureToTexture must have the same sample count.
+- for various source texture sample count
+- for various destination texture sample count
+`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcSampleCount', [1, 4]).
+combine('dstSampleCount', [1, 4])
+).
+fn((t) => {
+ const { srcSampleCount, dstSampleCount } = t.params;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: srcSampleCount
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: dstSampleCount
+ });
+
+ const isSuccess = srcSampleCount === dstSampleCount;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 4, height: 4, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('multisampled_copy_restrictions').
+desc(
+ `
+Test that copyTextureToTexture of multisampled texture must copy a whole subresource to a whole subresource.
+- for various origin for the source and destination of the copies.
+
+Note: this is only tested for 2D textures as it is the only dimension compatible with multisampling.
+TODO: Check the source and destination constraints separately.
+`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcCopyOrigin', [
+{ x: 0, y: 0, z: 0 },
+{ x: 1, y: 0, z: 0 },
+{ x: 0, y: 1, z: 0 },
+{ x: 1, y: 1, z: 0 }]
+).
+combine('dstCopyOrigin', [
+{ x: 0, y: 0, z: 0 },
+{ x: 1, y: 0, z: 0 },
+{ x: 0, y: 1, z: 0 },
+{ x: 1, y: 1, z: 0 }]
+).
+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])
+).
+fn((t) => {
+ const { srcCopyOrigin, dstCopyOrigin, copyWidth, copyHeight } = t.params;
+
+ const kWidth = 32;
+ const kHeight = 16;
+
+ // Currently we don't support multisampled 2D array textures and the mipmap level count of the
+ // multisampled textures must be 1.
+ const srcTexture = t.device.createTexture({
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4
+ });
+
+ const isSuccess = copyWidth === kWidth && copyHeight === kHeight;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: srcCopyOrigin },
+ { texture: dstTexture, origin: dstCopyOrigin },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('texture_format_compatibility').
+desc(
+ `
+Test the formats of textures in copyTextureToTexture must be copy-compatible.
+- for all source texture formats
+- for all destination texture formats
+`
+).
+params((u) =>
+u.
+combine('srcFormatFeature', kFeaturesForFormats).
+combine('dstFormatFeature', kFeaturesForFormats).
+beginSubcases().
+expand('srcFormat', ({ srcFormatFeature }) =>
+filterFormatsByFeature(srcFormatFeature, kTextureFormats)
+).
+expand('dstFormat', ({ dstFormatFeature }) =>
+filterFormatsByFeature(dstFormatFeature, kTextureFormats)
+)
+).
+beforeAllSubcases((t) => {
+ const { srcFormatFeature, dstFormatFeature } = t.params;
+ t.selectDeviceOrSkipTestCase([srcFormatFeature, dstFormatFeature]);
+}).
+fn((t) => {
+ const { srcFormat, dstFormat } = t.params;
+
+ t.skipIfTextureFormatNotSupported(srcFormat, dstFormat);
+ t.skipIfCopyTextureToTextureNotSupportedForFormat(srcFormat, dstFormat);
+
+ const srcFormatInfo = kTextureFormatInfo[srcFormat];
+ const dstFormatInfo = kTextureFormatInfo[dstFormat];
+
+ const textureSize = {
+ width: lcm(srcFormatInfo.blockWidth, dstFormatInfo.blockWidth),
+ height: lcm(srcFormatInfo.blockHeight, dstFormatInfo.blockHeight),
+ depthOrArrayLayers: 1
+ };
+
+ const srcTexture = t.device.createTexture({
+ size: textureSize,
+ format: srcFormat,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+
+ const dstTexture = t.device.createTexture({
+ size: textureSize,
+ format: dstFormat,
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ // Allow copy between compatible format textures.
+ const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat ?? srcFormat;
+ const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat ?? dstFormat;
+ const isSuccess = srcBaseFormat === dstBaseFormat;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ textureSize,
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('depth_stencil_copy_restrictions').
+desc(
+ `
+Test that depth textures subresources must be entirely copied in copyTextureToTexture
+- for various depth-stencil formats
+- for various copy origin and size offsets
+- for various source and destination texture sizes
+- for various source and destination mip levels
+
+Note: this is only tested for 2D textures as it is the only dimension compatible with depth-stencil.
+`
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+beginSubcases().
+combine('copyBoxOffsets', [
+{ x: 0, y: 0, width: 0, height: 0 },
+{ x: 1, y: 0, width: 0, height: 0 },
+{ x: 0, y: 1, width: 0, height: 0 },
+{ x: 0, y: 0, width: -1, height: 0 },
+{ x: 0, y: 0, width: 0, height: -1 }]
+).
+combine('srcTextureSize', [
+{ width: 64, height: 64, depthOrArrayLayers: 1 },
+{ width: 64, height: 32, depthOrArrayLayers: 1 },
+{ width: 32, height: 32, depthOrArrayLayers: 1 }]
+).
+combine('dstTextureSize', [
+{ width: 64, height: 64, depthOrArrayLayers: 1 },
+{ width: 64, height: 32, depthOrArrayLayers: 1 },
+{ width: 32, height: 32, depthOrArrayLayers: 1 }]
+).
+combine('srcCopyLevel', [1, 2]).
+combine('dstCopyLevel', [0, 1])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn((t) => {
+ const { format, copyBoxOffsets, srcTextureSize, dstTextureSize, srcCopyLevel, dstCopyLevel } =
+ t.params;
+ const kMipLevelCount = 3;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: srcTextureSize.width, height: srcTextureSize.height, depthOrArrayLayers: 1 },
+ format,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: dstTextureSize.width, height: dstTextureSize.height, depthOrArrayLayers: 1 },
+ format,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ const srcSizeAtLevel = t.GetPhysicalSubresourceSize('2d', srcTextureSize, format, srcCopyLevel);
+ const dstSizeAtLevel = t.GetPhysicalSubresourceSize('2d', dstTextureSize, format, dstCopyLevel);
+
+ const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: 0 };
+
+ const copyWidth =
+ Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x;
+ const copyHeight =
+ Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y;
+
+ // Depth/stencil copies must copy whole subresources.
+ const isSuccess =
+ copyOrigin.x === 0 &&
+ copyOrigin.y === 0 &&
+ copyWidth === srcSizeAtLevel.width &&
+ copyHeight === srcSizeAtLevel.height &&
+ copyWidth === dstSizeAtLevel.width &&
+ copyHeight === dstSizeAtLevel.height;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('copy_ranges').
+desc(
+ `
+Test that copyTextureToTexture copy boxes must be in range of the subresource.
+- for various dimensions
+- for various offsets to a full copy for the copy origin/size
+- for various copy mip levels
+`
+).
+params((u) =>
+u.
+combine('dimension', kTextureDimensions)
+//.beginSubcases()
+.combine('copyBoxOffsets', [
+{ x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 1, y: 0, z: 0, width: -1, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 1, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 1, z: 0, width: 0, height: -1, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 1, width: 0, height: 1, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 2, width: 0, height: 1, depthOrArrayLayers: 0 },
+{ x: 0, y: 0, z: 0, width: 1, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: 0, height: 1, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 1 },
+{ x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 0 },
+{ x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 },
+{ x: 0, y: 0, z: 2, width: 0, height: 0, depthOrArrayLayers: -1 }]
+).
+unless(
+ (p) =>
+ p.dimension === '1d' && (
+ p.copyBoxOffsets.y !== 0 ||
+ p.copyBoxOffsets.z !== 0 ||
+ p.copyBoxOffsets.height !== 0 ||
+ p.copyBoxOffsets.depthOrArrayLayers !== 0)
+).
+combine('srcCopyLevel', [0, 1, 3]).
+combine('dstCopyLevel', [0, 1, 3]).
+unless((p) => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0))
+).
+fn((t) => {
+ const { dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params;
+
+ const textureSize = { width: 16, height: 8, depthOrArrayLayers: 3 };
+ let mipLevelCount = 4;
+ if (dimension === '1d') {
+ mipLevelCount = 1;
+ textureSize.height = 1;
+ textureSize.depthOrArrayLayers = 1;
+ }
+ const kFormat = 'rgba8unorm';
+
+ const srcTexture = t.device.createTexture({
+ size: textureSize,
+ format: kFormat,
+ dimension,
+ mipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ const dstTexture = t.device.createTexture({
+ size: textureSize,
+ format: kFormat,
+ dimension,
+ mipLevelCount,
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ const srcSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ textureSize,
+ kFormat,
+ srcCopyLevel
+ );
+ const dstSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ textureSize,
+ kFormat,
+ dstCopyLevel
+ );
+
+ const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z };
+
+ const copyWidth = Math.max(
+ Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x,
+ 0
+ );
+ const copyHeight = Math.max(
+ Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y,
+ 0
+ );
+ const copyDepth =
+ textureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z;
+
+ {
+ let isSuccess =
+ copyWidth <= srcSizeAtLevel.width &&
+ copyHeight <= srcSizeAtLevel.height &&
+ copyOrigin.x + copyWidth <= dstSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= dstSizeAtLevel.height;
+
+ if (dimension === '3d') {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= srcSizeAtLevel.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= dstSizeAtLevel.depthOrArrayLayers;
+ } else {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= textureSize.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers;
+ }
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+
+ {
+ let isSuccess =
+ copyOrigin.x + copyWidth <= srcSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= srcSizeAtLevel.height &&
+ copyWidth <= dstSizeAtLevel.width &&
+ copyHeight <= dstSizeAtLevel.height;
+
+ if (dimension === '3d') {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= dstSizeAtLevel.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= srcSizeAtLevel.depthOrArrayLayers;
+ } else {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= textureSize.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers;
+ }
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+});
+
+g.test('copy_within_same_texture').
+desc(
+ `
+Test that it is an error to use copyTextureToTexture from one subresource to itself.
+- for various starting source/destination array layers.
+- for various copy sizes in number of array layers
+
+TODO: Extend to check the copy is allowed between different mip levels.
+TODO: Extend to 1D and 3D textures.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('srcCopyOriginZ', [0, 2, 4]).
+combine('dstCopyOriginZ', [0, 2, 4]).
+combine('copyExtentDepth', [1, 2, 3])
+).
+fn((t) => {
+ const { srcCopyOriginZ, dstCopyOriginZ, copyExtentDepth } = t.params;
+
+ const kArrayLayerCount = 7;
+
+ const testTexture = t.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: kArrayLayerCount },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const isSuccess =
+ Math.min(srcCopyOriginZ, dstCopyOriginZ) + copyExtentDepth <=
+ Math.max(srcCopyOriginZ, dstCopyOriginZ);
+ t.TestCopyTextureToTexture(
+ { texture: testTexture, origin: { x: 0, y: 0, z: srcCopyOriginZ } },
+ { texture: testTexture, origin: { x: 0, y: 0, z: dstCopyOriginZ } },
+ { width: 16, height: 16, depthOrArrayLayers: copyExtentDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('copy_aspects').
+desc(
+ `
+Test the validations on the member 'aspect' of GPUImageCopyTexture in CopyTextureToTexture().
+- for all the color and depth-stencil formats: the texture copy aspects must be both 'all'.
+- for all the depth-only formats: the texture copy aspects must be either 'all' or 'depth-only'.
+- for all the stencil-only formats: the texture copy aspects must be either 'all' or 'stencil-only'.
+`
+).
+params((u) =>
+u.
+combine('format', ['rgba8unorm', ...kDepthStencilFormats]).
+beginSubcases().
+combine('sourceAspect', ['all', 'depth-only', 'stencil-only']).
+combine('destinationAspect', ['all', 'depth-only', 'stencil-only'])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn((t) => {
+ const { format, sourceAspect, destinationAspect } = t.params;
+
+ const kTextureSize = { width: 16, height: 8, depthOrArrayLayers: 1 };
+
+ const srcTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ const dstTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ // MAINTENANCE_TODO: get the valid aspects from capability_info.ts.
+ const kValidAspectsForFormat = {
+ rgba8unorm: ['all'],
+
+ // kUnsizedDepthStencilFormats
+ depth24plus: ['all', 'depth-only'],
+ 'depth24plus-stencil8': ['all'],
+ 'depth32float-stencil8': ['all'],
+
+ // kSizedDepthStencilFormats
+ depth32float: ['all', 'depth-only'],
+ stencil8: ['all', 'stencil-only'],
+ depth16unorm: ['all', 'depth-only']
+ };
+
+ const isSourceAspectValid = kValidAspectsForFormat[format].includes(sourceAspect);
+ const isDestinationAspectValid = kValidAspectsForFormat[format].includes(destinationAspect);
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, aspect: sourceAspect },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, aspect: destinationAspect },
+ kTextureSize,
+ isSourceAspectValid && isDestinationAspectValid ? 'Success' : 'FinishError'
+ );
+});
+
+g.test('copy_ranges_with_compressed_texture_formats').
+desc(
+ `
+Test that copyTextureToTexture copy boxes must be in range of the subresource and aligned to the block size
+- for various dimensions
+- for various offsets to a full copy for the copy origin/size
+- for various copy mip levels
+
+TODO: Express the offsets in "block size" so as to be able to test non-4x4 compressed formats
+`
+).
+params((u) =>
+u.
+combine('format', kCompressedTextureFormats).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combine('copyBoxOffsets', [
+{ x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 4, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: -1, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: -4, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 1, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 4, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: 0, height: -1, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: 0, height: -4, depthOrArrayLayers: -2 },
+{ x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 0 },
+{ x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 }]
+).
+combine('srcCopyLevel', [0, 1, 2]).
+combine('dstCopyLevel', [0, 1, 2])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+ t.skipIfCopyTextureToTextureNotSupportedForFormat(format);
+}).
+fn((t) => {
+ const { format, dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ const kTextureSize = {
+ width: 15 * blockWidth,
+ height: 12 * blockHeight,
+ depthOrArrayLayers: 3
+ };
+ const kMipLevelCount = 4;
+
+ const srcTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ dimension,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ const dstTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ dimension,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ const srcSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ kTextureSize,
+ format,
+ srcCopyLevel
+ );
+ const dstSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ kTextureSize,
+ format,
+ dstCopyLevel
+ );
+
+ const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z };
+
+ const copyWidth = Math.max(
+ Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x,
+ 0
+ );
+ const copyHeight = Math.max(
+ Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y,
+ 0
+ );
+ const copyDepth =
+ kTextureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z;
+
+ const texelBlockWidth = kTextureFormatInfo[format].blockWidth;
+ const texelBlockHeight = kTextureFormatInfo[format].blockHeight;
+
+ const isSuccessForCompressedFormats =
+ copyOrigin.x % texelBlockWidth === 0 &&
+ copyOrigin.y % texelBlockHeight === 0 &&
+ copyWidth % texelBlockWidth === 0 &&
+ copyHeight % texelBlockHeight === 0;
+
+ {
+ const isSuccess =
+ isSuccessForCompressedFormats &&
+ copyWidth <= srcSizeAtLevel.width &&
+ copyHeight <= srcSizeAtLevel.height &&
+ copyOrigin.x + copyWidth <= dstSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= dstSizeAtLevel.height &&
+ copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+
+ {
+ const isSuccess =
+ isSuccessForCompressedFormats &&
+ copyOrigin.x + copyWidth <= srcSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= srcSizeAtLevel.height &&
+ copyWidth <= dstSizeAtLevel.width &&
+ copyHeight <= dstSizeAtLevel.height &&
+ copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/debug.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/debug.spec.js
new file mode 100644
index 0000000000..98dcdf2dc0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/debug.spec.js
@@ -0,0 +1,66 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+API validation test for debug groups and markers
+
+Test Coverage:
+ - For each encoder type (GPUCommandEncoder, GPUComputeEncoder, GPURenderPassEncoder,
+ GPURenderBundleEncoder):
+ - Test that all pushDebugGroup must have a corresponding popDebugGroup
+ - Push and pop counts of 0, 1, and 2 will be used.
+ - An error must be generated for non matching counts.
+ - Test calling pushDebugGroup with empty and non-empty strings.
+ - Test inserting a debug marker with empty and non-empty strings.
+ - Test strings with \0 in them.
+ - Test non-ASCII strings.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kEncoderTypes } from '../../../../util/command_buffer_maker.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('debug_group_balanced').
+params((u) =>
+u.
+combine('encoderType', kEncoderTypes).
+beginSubcases().
+combine('pushCount', [0, 1, 2]).
+combine('popCount', [0, 1, 2])
+).
+fn((t) => {
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(t.params.encoderType);
+ for (let i = 0; i < t.params.pushCount; ++i) {
+ encoder.pushDebugGroup(`${i}`);
+ }
+ for (let i = 0; i < t.params.popCount; ++i) {
+ encoder.popDebugGroup();
+ }
+ validateFinishAndSubmit(t.params.pushCount === t.params.popCount, true);
+});
+
+g.test('debug_group').
+params((u) =>
+u //
+.combine('encoderType', kEncoderTypes).
+beginSubcases().
+combine('label', ['', 'group', 'null\0in\0group\0label', '\0null at beginning', '🌞👆'])
+).
+fn((t) => {
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(t.params.encoderType);
+ encoder.pushDebugGroup(t.params.label);
+ encoder.popDebugGroup();
+ validateFinishAndSubmit(true, true);
+});
+
+g.test('debug_marker').
+params((u) =>
+u //
+.combine('encoderType', kEncoderTypes).
+beginSubcases().
+combine('label', ['', 'marker', 'null\0in\0marker', '\0null at beginning', '🌞👆'])
+).
+fn((t) => {
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(t.params.encoderType);
+ encoder.insertDebugMarker(t.params.label);
+ validateFinishAndSubmit(true, true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js
new file mode 100644
index 0000000000..744c7e20de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/index_access.spec.js
@@ -0,0 +1,162 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for indexed draws accessing the index buffer.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ createIndexBuffer(indexData) {
+ return this.makeBufferWithContents(new Uint32Array(indexData), GPUBufferUsage.INDEX);
+ }
+
+ createRenderPipeline() {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: {
+ topology: 'triangle-strip',
+ stripIndexFormat: 'uint32'
+ }
+ });
+ }
+
+ beginRenderPass(encoder) {
+ const colorAttachment = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ return encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+
+ drawIndexed(
+ indexBuffer,
+ indexCount,
+ instanceCount,
+ firstIndex,
+ baseVertex,
+ firstInstance,
+ isSuccess)
+ {
+ const pipeline = this.createRenderPipeline();
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = this.beginRenderPass(encoder);
+ pass.setPipeline(pipeline);
+ pass.setIndexBuffer(indexBuffer, 'uint32');
+ pass.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
+ pass.end();
+
+ if (isSuccess) {
+ this.device.queue.submit([encoder.finish()]);
+ } else {
+ this.expectValidationError(() => {
+ encoder.finish();
+ });
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('out_of_bounds').
+desc(
+ `Test drawing with out of bound index access to make sure encoder validation catch the
+ following indexCount and firstIndex OOB conditions
+ - either is within bound but indexCount + firstIndex is out of bound
+ - only firstIndex is out of bound
+ - only indexCount is out of bound
+ - firstIndex much larger than indexCount
+ - indexCount much larger than firstIndex
+ - max uint32 value for both to make sure the sum doesn't overflow
+ - max uint32 indexCount and small firstIndex
+ - max uint32 firstIndex and small indexCount
+ Together with normal and large instanceCount`
+).
+params(
+ (u) =>
+ u.
+ combineWithParams([
+ { indexCount: 6, firstIndex: 0 }, // draw all 6 out of 6 index
+ { indexCount: 5, firstIndex: 1 }, // draw the last 5 out of 6 index
+ { indexCount: 1, firstIndex: 5 }, // draw the last 1 out of 6 index
+ { indexCount: 0, firstIndex: 6 }, // firstIndex point to the one after last, but (indexCount + firstIndex) * stride <= bufferSize, valid
+ { indexCount: 0, firstIndex: 7 }, // (indexCount + firstIndex) * stride > bufferSize, invalid
+ { indexCount: 7, firstIndex: 0 }, // only indexCount out of bound
+ { indexCount: 6, firstIndex: 1 }, // indexCount + firstIndex out of bound
+ { indexCount: 1, firstIndex: 6 }, // indexCount valid, but (indexCount + firstIndex) out of bound
+ { indexCount: 6, firstIndex: 10000 }, // firstIndex much larger than the bound
+ { indexCount: 10000, firstIndex: 0 }, // indexCount much larger than the bound
+ { indexCount: 0xffffffff, firstIndex: 0xffffffff }, // max uint32 value
+ { indexCount: 0xffffffff, firstIndex: 2 }, // max uint32 indexCount and small firstIndex
+ { indexCount: 2, firstIndex: 0xffffffff } // small indexCount and max uint32 firstIndex
+ ]).
+ combine('instanceCount', [1, 10000]) // normal and large instanceCount
+).
+fn((t) => {
+ const { indexCount, firstIndex, instanceCount } = t.params;
+
+ const indexBuffer = t.createIndexBuffer([0, 1, 2, 3, 1, 2]);
+ const isSuccess = indexCount + firstIndex <= 6;
+
+ t.drawIndexed(indexBuffer, indexCount, instanceCount, firstIndex, 0, 0, isSuccess);
+});
+
+g.test('out_of_bounds_zero_sized_index_buffer').
+desc(
+ `Test drawing with an empty index buffer to make sure the encoder validation catch the
+ following indexCount and firstIndex conditions
+ - indexCount + firstIndex is out of bound
+ - indexCount is 0 but firstIndex is out of bound
+ - only indexCount is out of bound
+ - both are 0s (not out of bound) but index buffer size is 0
+ Together with normal and large instanceCount`
+).
+params(
+ (u) =>
+ u.
+ combineWithParams([
+ { indexCount: 3, firstIndex: 1 }, // indexCount + firstIndex out of bound
+ { indexCount: 0, firstIndex: 1 }, // indexCount is 0 but firstIndex out of bound
+ { indexCount: 3, firstIndex: 0 }, // only indexCount out of bound
+ { indexCount: 0, firstIndex: 0 } // just zeros, valid
+ ]).
+ combine('instanceCount', [1, 10000]) // normal and large instanceCount
+).
+fn((t) => {
+ const { indexCount, firstIndex, instanceCount } = t.params;
+
+ const indexBuffer = t.createIndexBuffer([]);
+ const isSuccess = indexCount + firstIndex <= 0;
+
+ t.drawIndexed(indexBuffer, indexCount, instanceCount, firstIndex, 0, 0, isSuccess);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/draw.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/draw.spec.js
new file mode 100644
index 0000000000..823a327118
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/draw.spec.js
@@ -0,0 +1,877 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Here we test the validation for draw functions, mainly the buffer access validation. All four types
+of draw calls are tested, and test that validation errors do / don't occur for certain call type
+and parameters as expect.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { kVertexFormatInfo } from '../../../../../capability_info.js';
+
+import { ValidationTest } from '../../../validation_test.js';
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+function callDrawIndexed(
+test,
+encoder,
+drawType,
+param)
+{
+ switch (drawType) {
+ case 'drawIndexed':{
+ encoder.drawIndexed(
+ param.indexCount,
+ param.instanceCount ?? 1,
+ param.firstIndex ?? 0,
+ param.baseVertex ?? 0,
+ param.firstInstance ?? 0
+ );
+ break;
+ }
+ case 'drawIndexedIndirect':{
+ const indirectArray = new Int32Array([
+ param.indexCount,
+ param.instanceCount ?? 1,
+ param.firstIndex ?? 0,
+ param.baseVertex ?? 0,
+ param.firstInstance ?? 0]
+ );
+ const indirectBuffer = test.makeBufferWithContents(indirectArray, GPUBufferUsage.INDIRECT);
+ encoder.drawIndexedIndirect(indirectBuffer, 0);
+ break;
+ }
+ }
+}
+
+
+
+
+
+
+
+function callDraw(
+test,
+encoder,
+drawType,
+param)
+{
+ switch (drawType) {
+ case 'draw':{
+ encoder.draw(
+ param.vertexCount,
+ param.instanceCount ?? 1,
+ param.firstVertex ?? 0,
+ param.firstInstance ?? 0
+ );
+ break;
+ }
+ case 'drawIndirect':{
+ const indirectArray = new Int32Array([
+ param.vertexCount,
+ param.instanceCount ?? 1,
+ param.firstVertex ?? 0,
+ param.firstInstance ?? 0]
+ );
+ const indirectBuffer = test.makeBufferWithContents(indirectArray, GPUBufferUsage.INDIRECT);
+ encoder.drawIndirect(indirectBuffer, 0);
+ break;
+ }
+ }
+}
+
+function makeTestPipeline(
+test,
+buffers)
+
+
+
+
+
+
+{
+ const bufferLayouts = [];
+ for (const b of buffers) {
+ bufferLayouts[b.slot] = b;
+ }
+
+ return test.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: test.device.createShaderModule({
+ code: test.getNoOpShaderCode('VERTEX')
+ }),
+ entryPoint: 'main',
+ buffers: bufferLayouts
+ },
+ fragment: {
+ module: test.device.createShaderModule({
+ code: test.getNoOpShaderCode('FRAGMENT')
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+}
+
+function makeTestPipelineWithVertexAndInstanceBuffer(
+test,
+arrayStride,
+attributeFormat,
+attributeOffset = 0)
+{
+ const vertexBufferLayouts = [
+ {
+ slot: 1,
+ stepMode: 'vertex',
+ arrayStride,
+ attributes: [
+ {
+ shaderLocation: 2,
+ format: attributeFormat,
+ offset: attributeOffset
+ }]
+
+ },
+ {
+ slot: 7,
+ stepMode: 'instance',
+ arrayStride,
+ attributes: [
+ {
+ shaderLocation: 6,
+ format: attributeFormat,
+ offset: attributeOffset
+ }]
+
+ }];
+
+
+ return makeTestPipeline(test, vertexBufferLayouts);
+}
+
+// Default parameters for all kind of draw call, arbitrary non-zero values that is not very large.
+const kDefaultParameterForDraw = {
+ instanceCount: 100,
+ firstInstance: 100
+};
+
+// Default parameters for non-indexed draw, arbitrary non-zero values that is not very large.
+const kDefaultParameterForNonIndexedDraw = {
+ vertexCount: 100,
+ firstVertex: 100
+};
+
+// Default parameters for indexed draw call and required index buffer, arbitrary non-zero values
+// that is not very large.
+const kDefaultParameterForIndexedDraw = {
+ indexCount: 100,
+ firstIndex: 100,
+ baseVertex: 100,
+ indexFormat: 'uint16',
+ indexBufferSize: 2 * 200 // exact required bound size for index buffer
+};
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test(`unused_buffer_bound`).
+desc(
+ `
+In this test we test that a small buffer bound to unused buffer slot won't cause validation error.
+- All draw commands,
+ - An unused {index , vertex} buffer with uselessly small range is bound (immediately before draw
+ call)
+`
+).
+params((u) =>
+u //
+.combine('smallIndexBuffer', [false, true]).
+combine('smallVertexBuffer', [false, true]).
+combine('smallInstanceBuffer', [false, true]).
+beginSubcases().
+combine('drawType', ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect']).
+unless(
+ // Always provide index buffer of enough size if it is used by indexed draw
+ (p) =>
+ p.smallIndexBuffer && (
+ p.drawType === 'drawIndexed' || p.drawType === 'drawIndexedIndirect')
+).
+combine('bufferOffset', [0, 4]).
+combine('boundSize', [0, 1])
+).
+fn((t) => {
+ const {
+ smallIndexBuffer,
+ smallVertexBuffer,
+ smallInstanceBuffer,
+ drawType,
+ bufferOffset,
+ boundSize
+ } = t.params;
+ const renderPipeline = t.createNoOpRenderPipeline();
+ const bufferSize = bufferOffset + boundSize;
+ const smallBuffer = t.createBufferWithState('valid', {
+ size: bufferSize,
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.VERTEX
+ });
+
+ // An index buffer of enough size, used if smallIndexBuffer === false
+ const { indexFormat, indexBufferSize } = kDefaultParameterForIndexedDraw;
+ const indexBuffer = t.createBufferWithState('valid', {
+ size: indexBufferSize,
+ usage: GPUBufferUsage.INDEX
+ });
+
+ for (const encoderType of ['render bundle', 'render pass']) {
+ for (const setPipelineBeforeBuffer of [false, true]) {
+ const commandBufferMaker = t.createEncoder(encoderType);
+ const renderEncoder = commandBufferMaker.encoder;
+
+ if (setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+
+ if (drawType === 'drawIndexed' || drawType === 'drawIndexedIndirect') {
+ // Always use large enough index buffer for indexed draw. Index buffer OOB validation is
+ // tested in index_buffer_OOB.
+ renderEncoder.setIndexBuffer(indexBuffer, indexFormat, 0, indexBufferSize);
+ } else if (smallIndexBuffer) {
+ renderEncoder.setIndexBuffer(smallBuffer, indexFormat, bufferOffset, boundSize);
+ }
+ if (smallVertexBuffer) {
+ renderEncoder.setVertexBuffer(1, smallBuffer, bufferOffset, boundSize);
+ }
+ if (smallInstanceBuffer) {
+ renderEncoder.setVertexBuffer(7, smallBuffer, bufferOffset, boundSize);
+ }
+
+ if (!setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+
+ if (drawType === 'draw' || drawType === 'drawIndirect') {
+ const drawParam = {
+ ...kDefaultParameterForDraw,
+ ...kDefaultParameterForNonIndexedDraw
+ };
+ callDraw(t, renderEncoder, drawType, drawParam);
+ } else {
+ const drawParam = {
+ ...kDefaultParameterForDraw,
+ ...kDefaultParameterForIndexedDraw
+ };
+ callDrawIndexed(t, renderEncoder, drawType, drawParam);
+ }
+
+ // Binding a unused small index/vertex buffer will never cause validation error.
+ commandBufferMaker.validateFinishAndSubmit(true, true);
+ }
+ }
+});
+
+g.test(`index_buffer_OOB`).
+desc(
+ `
+In this test we test that index buffer OOB is caught as a validation error in drawIndexed, but not in
+drawIndexedIndirect as it is GPU-validated.
+- Issue an indexed draw call, with the following index buffer states, for {all index formats}:
+ - range and GPUBuffer are exactly the required size for the draw call
+ - range is too small but GPUBuffer is still large enough
+ - range and GPUBuffer are both too small
+`
+).
+params((u) =>
+u.
+combine('bufferSizeInElements', [10, 100])
+// Binding size is always no larger than buffer size, make sure that setIndexBuffer succeed
+.combine('bindingSizeInElements', [10]).
+combine('drawIndexCount', [10, 11]).
+combine('drawType', ['drawIndexed', 'drawIndexedIndirect']).
+beginSubcases().
+combine('indexFormat', ['uint16', 'uint32'])
+).
+fn((t) => {
+ const { indexFormat, bindingSizeInElements, bufferSizeInElements, drawIndexCount, drawType } =
+ t.params;
+
+ const indexElementSize = indexFormat === 'uint16' ? 2 : 4;
+ const bindingSize = bindingSizeInElements * indexElementSize;
+ const bufferSize = bufferSizeInElements * indexElementSize;
+
+ const desc = {
+ size: bufferSize,
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
+ };
+ const indexBuffer = t.createBufferWithState('valid', desc);
+
+ const drawCallParam = {
+ indexCount: drawIndexCount
+ };
+
+ // Encoder finish will succeed if no index buffer access OOB when calling drawIndexed,
+ // and always succeed when calling drawIndexedIndirect.
+ const isFinishSuccess =
+ drawIndexCount <= bindingSizeInElements || drawType === 'drawIndexedIndirect';
+
+ const renderPipeline = t.createNoOpRenderPipeline();
+
+ for (const encoderType of ['render bundle', 'render pass']) {
+ for (const setPipelineBeforeBuffer of [false, true]) {
+ const commandBufferMaker = t.createEncoder(encoderType);
+ const renderEncoder = commandBufferMaker.encoder;
+
+ if (setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+ renderEncoder.setIndexBuffer(indexBuffer, indexFormat, 0, bindingSize);
+ if (!setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+
+ callDrawIndexed(t, renderEncoder, drawType, drawCallParam);
+
+ commandBufferMaker.validateFinishAndSubmit(isFinishSuccess, true);
+ }
+ }
+});
+
+g.test(`vertex_buffer_OOB`).
+desc(
+ `
+In this test we test the vertex buffer OOB validation in draw calls. Specifically, only vertex step
+mode buffer OOB in draw and instance step mode buffer OOB in draw and drawIndexed are CPU-validated.
+Other cases are handled by robust access and no validation error occurs.
+- Test that:
+ - Draw call needs to read {=, >} any bound vertex buffer range, with GPUBuffer that is {large
+ enough, exactly the size of bound range}
+ - Binding size = 0 (ensure it's not treated as a special case)
+ - x= weird buffer offset values
+ - x= weird attribute offset values
+ - x= weird arrayStride values
+ - x= {render pass, render bundle}
+- For vertex step mode vertex buffer,
+ - Test that:
+ - vertexCount largeish
+ - firstVertex {=, >} 0
+ - arrayStride is 0 and bound buffer size too small
+ - (vertexCount + firstVertex) is zero
+ - Validation error occurs in:
+ - draw
+ - drawIndexed with a zero array stride vertex step mode buffer OOB
+ - Otherwise no validation error in drawIndexed, draIndirect and drawIndexedIndirect
+- For instance step mode vertex buffer,
+ - Test with draw and drawIndexed:
+ - instanceCount largeish
+ - firstInstance {=, >} 0
+ - arrayStride is 0 and bound buffer size too small
+ - (instanceCount + firstInstance) is zero
+ - Validation error occurs in draw and drawIndexed
+ - No validation error in drawIndirect and drawIndexedIndirect
+
+In this test, we use a a render pipeline requiring one vertex step mode with different vertex buffer
+layouts (attribute offset, array stride, vertex format). Then for a given drawing parameter set (e.g.,
+vertexCount, instanceCount, firstVertex, indexCount), we calculate the exactly required size for
+vertex step mode vertex buffer. Then, we generate buffer parameters (i.e. GPU buffer size,
+binding offset and binding size) for all buffers, covering both (bound size == required size),
+(bound size == required size - 1), and (bound size == 0), and test that draw and drawIndexed will
+success/error as expected. Such set of buffer parameters should include cases like weird offset values.
+`
+).
+params((u) =>
+u
+// type of draw call
+.combine('type', ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'])
+// VBSize: the state of vertex step mode vertex buffer bound size
+// IBSize: the state of instance step mode vertex buffer bound size
+.combineWithParams([
+{ VBSize: 'exact', IBSize: 'exact' },
+{ VBSize: 'zero', IBSize: 'exact' },
+{ VBSize: 'oneTooSmall', IBSize: 'exact' },
+{ VBSize: 'exact', IBSize: 'zero' },
+{ VBSize: 'exact', IBSize: 'oneTooSmall' }]
+)
+// the state of array stride
+.combine('AStride', ['zero', 'exact', 'oversize']).
+beginSubcases()
+// should the vertex stride count be zero
+.combine('VStride0', [false, true])
+// should the instance stride count be zero
+.combine('IStride0', [false, true])
+// the factor for offset of attributes in vertex layout
+.combine('offset', [0, 1, 2, 7]) // the offset of attribute will be factor * MIN(4, sizeof(vertexFormat))
+.combine('setBufferOffset', [200]) // must be a multiple of 4
+.combine('attributeFormat', ['snorm8x2', 'float32', 'float16x4']).
+expandWithParams((p) =>
+p.VStride0 ?
+[{ firstVertex: 0, vertexCount: 0 }] :
+[
+{ firstVertex: 0, vertexCount: 1 },
+{ firstVertex: 0, vertexCount: 10000 },
+{ firstVertex: 10000, vertexCount: 0 },
+{ firstVertex: 10000, vertexCount: 10000 }]
+
+).
+expandWithParams((p) =>
+p.IStride0 ?
+[{ firstInstance: 0, instanceCount: 0 }] :
+[
+{ firstInstance: 0, instanceCount: 1 },
+{ firstInstance: 0, instanceCount: 10000 },
+{ firstInstance: 10000, instanceCount: 0 },
+{ firstInstance: 10000, instanceCount: 10000 }]
+
+).
+unless((p) => p.vertexCount === 10000 && p.instanceCount === 10000)
+).
+fn((t) => {
+ const {
+ type: drawType,
+ VBSize: boundVertexBufferSizeState,
+ IBSize: boundInstanceBufferSizeState,
+ VStride0: zeroVertexStrideCount,
+ IStride0: zeroInstanceStrideCount,
+ AStride: arrayStrideState,
+ offset: attributeOffsetFactor,
+ setBufferOffset,
+ attributeFormat,
+ vertexCount,
+ instanceCount,
+ firstVertex,
+ firstInstance
+ } = t.params;
+
+ const attributeFormatInfo = kVertexFormatInfo[attributeFormat];
+ const formatSize = attributeFormatInfo.byteSize;
+ const attributeOffset = attributeOffsetFactor * Math.min(4, formatSize);
+ const lastStride = attributeOffset + formatSize;
+ let arrayStride = 0;
+ if (arrayStrideState !== 'zero') {
+ arrayStride = lastStride;
+ if (arrayStrideState === 'oversize') {
+ // Add an arbitrary number to array stride to make it larger than required by attributes
+ arrayStride = arrayStride + 20;
+ }
+ arrayStride = arrayStride + (-arrayStride & 3); // Make sure arrayStride is a multiple of 4
+ }
+
+ const calcSetBufferSize = (
+ boundBufferSizeState,
+ strideCount) =>
+ {
+ let requiredBufferSize;
+ if (strideCount > 0) {
+ requiredBufferSize = arrayStride * (strideCount - 1) + lastStride;
+ } else {
+ // Spec do not validate bounded buffer size if strideCount == 0.
+ requiredBufferSize = lastStride;
+ }
+ let setBufferSize;
+ switch (boundBufferSizeState) {
+ case 'zero':{
+ setBufferSize = 0;
+ break;
+ }
+ case 'oneTooSmall':{
+ setBufferSize = requiredBufferSize - 1;
+ break;
+ }
+ case 'exact':{
+ setBufferSize = requiredBufferSize;
+ break;
+ }
+ }
+ return setBufferSize;
+ };
+
+ const strideCountForVertexBuffer = firstVertex + vertexCount;
+ const setVertexBufferSize = calcSetBufferSize(
+ boundVertexBufferSizeState,
+ strideCountForVertexBuffer
+ );
+ const vertexBufferSize = setBufferOffset + setVertexBufferSize;
+ const strideCountForInstanceBuffer = firstInstance + instanceCount;
+ const setInstanceBufferSize = calcSetBufferSize(
+ boundInstanceBufferSizeState,
+ strideCountForInstanceBuffer
+ );
+ const instanceBufferSize = setBufferOffset + setInstanceBufferSize;
+
+ const vertexBuffer = t.createBufferWithState('valid', {
+ size: vertexBufferSize,
+ usage: GPUBufferUsage.VERTEX
+ });
+ const instanceBuffer = t.createBufferWithState('valid', {
+ size: instanceBufferSize,
+ usage: GPUBufferUsage.VERTEX
+ });
+
+ const renderPipeline = makeTestPipelineWithVertexAndInstanceBuffer(
+ t,
+ arrayStride,
+ attributeFormat,
+ attributeOffset
+ );
+
+ for (const encoderType of ['render bundle', 'render pass']) {
+ for (const setPipelineBeforeBuffer of [false, true]) {
+ const commandBufferMaker = t.createEncoder(encoderType);
+ const renderEncoder = commandBufferMaker.encoder;
+
+ if (setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+ renderEncoder.setVertexBuffer(1, vertexBuffer, setBufferOffset, setVertexBufferSize);
+ renderEncoder.setVertexBuffer(7, instanceBuffer, setBufferOffset, setInstanceBufferSize);
+ if (!setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+
+ if (drawType === 'draw' || drawType === 'drawIndirect') {
+ const drawParam = {
+ vertexCount,
+ instanceCount,
+ firstVertex,
+ firstInstance
+ };
+
+ callDraw(t, renderEncoder, drawType, drawParam);
+ } else {
+ const { indexFormat, indexCount, firstIndex, indexBufferSize } =
+ kDefaultParameterForIndexedDraw;
+
+ const desc = {
+ size: indexBufferSize,
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
+ };
+ const indexBuffer = t.createBufferWithState('valid', desc);
+
+ const drawParam = {
+ indexCount,
+ instanceCount,
+ firstIndex,
+ baseVertex: firstVertex,
+ firstInstance
+ };
+
+ renderEncoder.setIndexBuffer(indexBuffer, indexFormat, 0, indexBufferSize);
+ callDrawIndexed(t, renderEncoder, drawType, drawParam);
+ }
+
+ const isVertexBufferOOB =
+ boundVertexBufferSizeState !== 'exact' &&
+ drawType === 'draw' && // drawIndirect, drawIndexed, and drawIndexedIndirect do not validate vertex step mode buffer
+ !zeroVertexStrideCount; // vertex step mode buffer never OOB if stride count = 0
+ const isInstanceBufferOOB =
+ boundInstanceBufferSizeState !== 'exact' && (
+ drawType === 'draw' || drawType === 'drawIndexed') && // drawIndirect and drawIndexedIndirect do not validate instance step mode buffer
+ !zeroInstanceStrideCount; // vertex step mode buffer never OOB if stride count = 0
+ const isFinishSuccess = !isVertexBufferOOB && !isInstanceBufferOOB;
+
+ commandBufferMaker.validateFinishAndSubmit(isFinishSuccess, true);
+ }
+ }
+});
+
+g.test(`buffer_binding_overlap`).
+desc(
+ `
+In this test we test that binding one GPU buffer to multiple vertex buffer slot or both vertex
+buffer slot and index buffer will cause no validation error, with completely/partial overlap.
+ - x= all draw types
+
+ TODO: The "Factor" parameters don't necessarily guarantee that we test all configurations
+ of buffers overlapping or not. This test should be refactored to test specific overlap cases,
+ and have fewer total parameterizations.
+ See https://github.com/gpuweb/cts/pull/3122#discussion_r1378623214
+`
+).
+params((u) =>
+u //
+.combine('drawType', ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect']).
+beginSubcases().
+combine('vertexBoundOffestFactor', [0, 0.5, 1, 1.5, 2]).
+combine('instanceBoundOffestFactor', [0, 0.5, 1, 1.5, 2]).
+combine('indexBoundOffestFactor', [0, 0.5, 1, 1.5, 2]).
+combine('arrayStrideState', ['zero', 'exact', 'oversize'])
+).
+fn((t) => {
+ const {
+ drawType,
+ vertexBoundOffestFactor,
+ instanceBoundOffestFactor,
+ indexBoundOffestFactor,
+ arrayStrideState
+ } = t.params;
+
+ // Compute the array stride for vertex step mode and instance step mode attribute
+ const attributeFormat = 'float32x4';
+ const attributeFormatInfo = kVertexFormatInfo[attributeFormat];
+ const formatSize = attributeFormatInfo.byteSize;
+ const attributeOffset = 0;
+ const lastStride = attributeOffset + formatSize;
+ let arrayStride = 0;
+ if (arrayStrideState !== 'zero') {
+ arrayStride = lastStride;
+ if (arrayStrideState === 'oversize') {
+ // Add an arbitrary number to array stride
+ arrayStride = arrayStride + 20;
+ }
+ arrayStride = arrayStride + (-arrayStride & 3); // Make sure arrayStride is a multiple of 4
+ }
+
+ const calcAttributeBufferSize = (strideCount) => {
+ let requiredBufferSize;
+ if (strideCount > 0) {
+ requiredBufferSize = arrayStride * (strideCount - 1) + lastStride;
+ } else {
+ // Spec do not validate bounded buffer size if strideCount == 0.
+ requiredBufferSize = lastStride;
+ }
+ return requiredBufferSize;
+ };
+
+ const calcSetBufferOffset = (requiredSetBufferSize, offsetFactor) => {
+ const offset = Math.ceil(requiredSetBufferSize * offsetFactor);
+ const alignedOffset = offset + (-offset & 3); // Make sure offset is a multiple of 4
+ return alignedOffset;
+ };
+
+ // Compute required bound range for all vertex and index buffer to ensure the shared GPU buffer
+ // has enough size.
+ const { vertexCount, firstVertex } = kDefaultParameterForNonIndexedDraw;
+ const strideCountForVertexBuffer = firstVertex + vertexCount;
+ const setVertexBufferSize = calcAttributeBufferSize(strideCountForVertexBuffer);
+ const setVertexBufferOffset = calcSetBufferOffset(setVertexBufferSize, vertexBoundOffestFactor);
+ let requiredBufferSize = setVertexBufferOffset + setVertexBufferSize;
+
+ const { instanceCount, firstInstance } = kDefaultParameterForDraw;
+ const strideCountForInstanceBuffer = firstInstance + instanceCount;
+ const setInstanceBufferSize = calcAttributeBufferSize(strideCountForInstanceBuffer);
+ const setInstanceBufferOffset = calcSetBufferOffset(
+ setInstanceBufferSize,
+ instanceBoundOffestFactor
+ );
+ requiredBufferSize = Math.max(
+ requiredBufferSize,
+ setInstanceBufferOffset + setInstanceBufferSize
+ );
+
+ const { indexBufferSize: setIndexBufferSize, indexFormat } = kDefaultParameterForIndexedDraw;
+ const setIndexBufferOffset = calcSetBufferOffset(setIndexBufferSize, indexBoundOffestFactor);
+ requiredBufferSize = Math.max(requiredBufferSize, setIndexBufferOffset + setIndexBufferSize);
+
+ // Create the shared GPU buffer with both vertetx and index usage
+ const sharedBuffer = t.createBufferWithState('valid', {
+ size: requiredBufferSize,
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.INDEX
+ });
+
+ const renderPipeline = makeTestPipelineWithVertexAndInstanceBuffer(
+ t,
+ arrayStride,
+ attributeFormat
+ );
+
+ for (const encoderType of ['render bundle', 'render pass']) {
+ for (const setPipelineBeforeBuffer of [false, true]) {
+ const commandBufferMaker = t.createEncoder(encoderType);
+ const renderEncoder = commandBufferMaker.encoder;
+
+ if (setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+ renderEncoder.setVertexBuffer(1, sharedBuffer, setVertexBufferOffset, setVertexBufferSize);
+ renderEncoder.setVertexBuffer(
+ 7,
+ sharedBuffer,
+ setInstanceBufferOffset,
+ setInstanceBufferSize
+ );
+ renderEncoder.setIndexBuffer(
+ sharedBuffer,
+ indexFormat,
+ setIndexBufferOffset,
+ setIndexBufferSize
+ );
+ if (!setPipelineBeforeBuffer) {
+ renderEncoder.setPipeline(renderPipeline);
+ }
+
+ if (drawType === 'draw' || drawType === 'drawIndirect') {
+ const drawParam = {
+ ...kDefaultParameterForDraw,
+ ...kDefaultParameterForNonIndexedDraw
+ };
+ callDraw(t, renderEncoder, drawType, drawParam);
+ } else {
+ const drawParam = {
+ ...kDefaultParameterForDraw,
+ ...kDefaultParameterForIndexedDraw
+ };
+ callDrawIndexed(t, renderEncoder, drawType, drawParam);
+ }
+
+ // Since all bound buffer are of enough size, draw call should always succeed.
+ commandBufferMaker.validateFinishAndSubmit(true, true);
+ }
+ }
+});
+
+g.test(`last_buffer_setting_take_account`).
+desc(
+ `
+In this test we test that only the last setting for a buffer slot take account.
+- All (non/indexed, in/direct) draw commands
+ - setPl, setVB, setIB, draw, {setPl,setVB,setIB,nothing (control)}, then a larger draw that
+ wouldn't have been valid before that
+`
+).
+unimplemented();
+
+g.test(`max_draw_count`).
+desc(
+ `
+In this test we test that draw count which exceeds
+GPURenderPassDescriptor.maxDrawCount causes validation error on
+GPUCommandEncoder.finish(). The test sets specified maxDrawCount,
+calls specified draw call specified times with or without bundles,
+and checks whether GPUCommandEncoder.finish() causes a validation error.
+ - x= whether to use a bundle for the first half of the draw calls
+ - x= whether to use a bundle for the second half of the draw calls
+ - x= several different draw counts
+ - x= several different maxDrawCounts
+`
+).
+params((u) =>
+u.
+combine('bundleFirstHalf', [false, true]).
+combine('bundleSecondHalf', [false, true]).
+combine('maxDrawCount', [0, 1, 4, 16]).
+beginSubcases().
+expand('drawCount', (p) => new Set([0, p.maxDrawCount, p.maxDrawCount + 1]))
+).
+fn((t) => {
+ const { bundleFirstHalf, bundleSecondHalf, maxDrawCount, drawCount } = t.params;
+
+ const colorFormat = 'rgba8unorm';
+ const colorTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: colorFormat,
+ mipLevelCount: 1,
+ sampleCount: 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() {}`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorFormat, writeMask: 0 }]
+ }
+ });
+
+ const indexBuffer = t.makeBufferWithContents(new Uint16Array([0, 0, 0]), GPUBufferUsage.INDEX);
+ const indirectBuffer = t.makeBufferWithContents(
+ new Uint32Array([3, 1, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ );
+ const indexedIndirectBuffer = t.makeBufferWithContents(
+ new Uint32Array([3, 1, 0, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ );
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ maxDrawCount
+ });
+
+ const firstHalfEncoder = bundleFirstHalf ?
+ t.device.createRenderBundleEncoder({
+ colorFormats: [colorFormat]
+ }) :
+ renderPassEncoder;
+
+ const secondHalfEncoder = bundleSecondHalf ?
+ t.device.createRenderBundleEncoder({
+ colorFormats: [colorFormat]
+ }) :
+ renderPassEncoder;
+
+ firstHalfEncoder.setPipeline(pipeline);
+ firstHalfEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ secondHalfEncoder.setPipeline(pipeline);
+ secondHalfEncoder.setIndexBuffer(indexBuffer, 'uint16');
+
+ const halfDrawCount = Math.floor(drawCount / 2);
+ for (let i = 0; i < drawCount; i++) {
+ const encoder = i < halfDrawCount ? firstHalfEncoder : secondHalfEncoder;
+ if (i % 4 === 0) {
+ encoder.draw(3);
+ }
+ if (i % 4 === 1) {
+ encoder.drawIndexed(3);
+ }
+ if (i % 4 === 2) {
+ encoder.drawIndirect(indirectBuffer, 0);
+ }
+ if (i % 4 === 3) {
+ encoder.drawIndexedIndirect(indexedIndirectBuffer, 0);
+ }
+ }
+
+ const bundles = [];
+ if (bundleFirstHalf) {
+ bundles.push(firstHalfEncoder.finish());
+ }
+ if (bundleSecondHalf) {
+ bundles.push(secondHalfEncoder.finish());
+ }
+
+ if (bundles.length > 0) {
+ renderPassEncoder.executeBundles(bundles);
+ }
+
+ renderPassEncoder.end();
+
+ t.expectValidationError(() => {
+ commandEncoder.finish();
+ }, drawCount > maxDrawCount);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.js
new file mode 100644
index 0000000000..048f33029a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/dynamic_state.spec.js
@@ -0,0 +1,319 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+API validation tests for dynamic state commands (setViewport/ScissorRect/BlendColor...).
+
+TODO: ensure existing tests cover these notes. Note many of these may be operation tests instead.
+> - setViewport
+> - {x, y} = {0, invalid values if any}
+> - {width, height, minDepth, maxDepth} = {
+> - least possible value that's valid
+> - greatest possible negative value that's invalid
+> - greatest possible positive value that's valid
+> - least possible positive value that's invalid if any
+> - }
+> - minDepth {<, =, >} maxDepth
+> - setScissorRect
+> - {width, height} = 0
+> - {x+width, y+height} = attachment size + 1
+> - setBlendConstant
+> - color {slightly, very} out of range
+> - used with a simple pipeline that {does, doesn't} use it
+> - setStencilReference
+> - {0, max}
+> - used with a simple pipeline that {does, doesn't} use it
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../../validation_test.js';
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+class F extends ValidationTest {
+ testViewportCall(
+ success,
+ v,
+ attachmentSize = { width: 1, height: 1, depthOrArrayLayers: 1 })
+ {
+ const attachment = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: attachmentSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachment.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setViewport(v.x, v.y, v.w, v.h, v.minDepth, v.maxDepth);
+ pass.end();
+
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+
+ testScissorCall(
+ success,
+ s,
+ attachmentSize = { width: 1, height: 1, depthOrArrayLayers: 1 })
+ {
+ const attachment = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: attachmentSize,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachment.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ if (success === 'type-error') {
+ this.shouldThrow('TypeError', () => {
+ pass.setScissorRect(s.x, s.y, s.w, s.h);
+ });
+ } else {
+ pass.setScissorRect(s.x, s.y, s.w, s.h);
+ pass.end();
+
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+ }
+
+ createDummyRenderPassEncoder() {
+ const attachment = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: [1, 1, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachment.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+
+ return { encoder, pass };
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('setViewport,x_y_width_height_nonnegative').
+desc(
+ `Test that the parameters of setViewport to define the box must be non-negative.
+
+TODO Test -0 (it should be valid) but can't be tested because the harness complains about duplicate parameters.
+TODO Test the first value smaller than -0`
+).
+paramsSubcasesOnly([
+// Control case: everything to 0 is ok, covers the empty viewport case.
+{ x: 0, y: 0, w: 0, h: 0 },
+
+// Test -1
+{ x: -1, y: 0, w: 0, h: 0 },
+{ x: 0, y: -1, w: 0, h: 0 },
+{ x: 0, y: 0, w: -1, h: 0 },
+{ x: 0, y: 0, w: 0, h: -1 }]
+).
+fn((t) => {
+ const { x, y, w, h } = t.params;
+ const success = x >= 0 && y >= 0 && w >= 0 && h >= 0;
+ t.testViewportCall(success, { x, y, w, h, minDepth: 0, maxDepth: 1 });
+});
+
+g.test('setViewport,xy_rect_contained_in_attachment').
+desc(
+ 'Test that the rectangle defined by x, y, width, height must be contained in the attachments'
+).
+paramsSubcasesOnly((u) =>
+u.
+combineWithParams([
+{ attachmentWidth: 3, attachmentHeight: 5 },
+{ attachmentWidth: 5, attachmentHeight: 3 },
+{ attachmentWidth: 1024, attachmentHeight: 1 },
+{ attachmentWidth: 1, attachmentHeight: 1024 }]
+).
+combineWithParams([
+// Control case: a full viewport is valid.
+{ dx: 0, dy: 0, dw: 0, dh: 0 },
+
+// Other valid cases with a partial viewport.
+{ dx: 1, dy: 0, dw: -1, dh: 0 },
+{ dx: 0, dy: 1, dw: 0, dh: -1 },
+{ dx: 0, dy: 0, dw: -1, dh: 0 },
+{ dx: 0, dy: 0, dw: 0, dh: -1 },
+
+// Test with a small value that causes the viewport to go outside the attachment.
+{ dx: 1, dy: 0, dw: 0, dh: 0 },
+{ dx: 0, dy: 1, dw: 0, dh: 0 },
+{ dx: 0, dy: 0, dw: 1, dh: 0 },
+{ dx: 0, dy: 0, dw: 0, dh: 1 }]
+)
+).
+fn((t) => {
+ const { attachmentWidth, attachmentHeight, dx, dy, dw, dh } = t.params;
+ const x = dx;
+ const y = dy;
+ const w = attachmentWidth + dw;
+ const h = attachmentWidth + dh;
+
+ const success = x + w <= attachmentWidth && y + h <= attachmentHeight;
+ t.testViewportCall(
+ success,
+ { x, y, w, h, minDepth: 0, maxDepth: 1 },
+ { width: attachmentWidth, height: attachmentHeight, depthOrArrayLayers: 1 }
+ );
+});
+
+g.test('setViewport,depth_rangeAndOrder').
+desc('Test that 0 <= minDepth <= maxDepth <= 1').
+paramsSubcasesOnly([
+// Success cases
+{ minDepth: 0, maxDepth: 1 },
+{ minDepth: -0, maxDepth: -0 },
+{ minDepth: 1, maxDepth: 1 },
+{ minDepth: 0.3, maxDepth: 0.7 },
+{ minDepth: 0.7, maxDepth: 0.7 },
+{ minDepth: 0.3, maxDepth: 0.3 },
+
+// Invalid cases
+{ minDepth: -0.1, maxDepth: 1 },
+{ minDepth: 0, maxDepth: 1.1 },
+{ minDepth: 0.5, maxDepth: 0.49999 }]
+).
+fn((t) => {
+ const { minDepth, maxDepth } = t.params;
+ const success =
+ 0 <= minDepth && minDepth <= 1 && 0 <= maxDepth && maxDepth <= 1 && minDepth <= maxDepth;
+ t.testViewportCall(success, { x: 0, y: 0, w: 1, h: 1, minDepth, maxDepth });
+});
+
+g.test('setScissorRect,x_y_width_height_nonnegative').
+desc(
+ `Test that the parameters of setScissorRect to define the box must be non-negative or a TypeError is thrown.
+
+TODO Test -0 (it should be valid) but can't be tested because the harness complains about duplicate parameters.
+TODO Test the first value smaller than -0`
+).
+paramsSubcasesOnly([
+// Control case: everything to 0 is ok, covers the empty scissor case.
+{ x: 0, y: 0, w: 0, h: 0 },
+
+// Test -1
+{ x: -1, y: 0, w: 0, h: 0 },
+{ x: 0, y: -1, w: 0, h: 0 },
+{ x: 0, y: 0, w: -1, h: 0 },
+{ x: 0, y: 0, w: 0, h: -1 }]
+).
+fn((t) => {
+ const { x, y, w, h } = t.params;
+ const success = x >= 0 && y >= 0 && w >= 0 && h >= 0;
+ t.testScissorCall(success ? true : 'type-error', { x, y, w, h });
+});
+
+g.test('setScissorRect,xy_rect_contained_in_attachment').
+desc(
+ 'Test that the rectangle defined by x, y, width, height must be contained in the attachments'
+).
+paramsSubcasesOnly((u) =>
+u.
+combineWithParams([
+{ attachmentWidth: 3, attachmentHeight: 5 },
+{ attachmentWidth: 5, attachmentHeight: 3 },
+{ attachmentWidth: 1024, attachmentHeight: 1 },
+{ attachmentWidth: 1, attachmentHeight: 1024 }]
+).
+combineWithParams([
+// Control case: a full scissor is valid.
+{ dx: 0, dy: 0, dw: 0, dh: 0 },
+
+// Other valid cases with a partial scissor.
+{ dx: 1, dy: 0, dw: -1, dh: 0 },
+{ dx: 0, dy: 1, dw: 0, dh: -1 },
+{ dx: 0, dy: 0, dw: -1, dh: 0 },
+{ dx: 0, dy: 0, dw: 0, dh: -1 },
+
+// Test with a small value that causes the scissor to go outside the attachment.
+{ dx: 1, dy: 0, dw: 0, dh: 0 },
+{ dx: 0, dy: 1, dw: 0, dh: 0 },
+{ dx: 0, dy: 0, dw: 1, dh: 0 },
+{ dx: 0, dy: 0, dw: 0, dh: 1 }]
+)
+).
+fn((t) => {
+ const { attachmentWidth, attachmentHeight, dx, dy, dw, dh } = t.params;
+ const x = dx;
+ const y = dy;
+ const w = attachmentWidth + dw;
+ const h = attachmentWidth + dh;
+
+ const success = x + w <= attachmentWidth && y + h <= attachmentHeight;
+ t.testScissorCall(
+ success,
+ { x, y, w, h },
+ { width: attachmentWidth, height: attachmentHeight, depthOrArrayLayers: 1 }
+ );
+});
+
+g.test('setBlendConstant').
+desc('Test that almost any color value is valid for setBlendConstant').
+paramsSubcasesOnly([
+{ r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
+{ r: -1.0, g: -1.0, b: -1.0, a: -1.0 },
+{ r: Number.MAX_SAFE_INTEGER, g: Number.MIN_SAFE_INTEGER, b: -0, a: 100000 }]
+).
+fn((t) => {
+ const { r, g, b, a } = t.params;
+ const encoders = t.createDummyRenderPassEncoder();
+ encoders.pass.setBlendConstant({ r, g, b, a });
+ encoders.pass.end();
+ encoders.encoder.finish();
+});
+
+g.test('setStencilReference').
+desc('Test that almost any stencil reference value is valid for setStencilReference').
+paramsSubcasesOnly([
+{ value: 1 }, //
+{ value: 0 },
+{ value: 1000 },
+{ value: 0xffffffff }]
+).
+fn((t) => {
+ const { value } = t.params;
+ const encoders = t.createDummyRenderPassEncoder();
+ encoders.pass.setStencilReference(value);
+ encoders.pass.end();
+ encoders.encoder.finish();
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/indirect_draw.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/indirect_draw.spec.js
new file mode 100644
index 0000000000..476f2ba73a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/indirect_draw.spec.js
@@ -0,0 +1,202 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for drawIndirect/drawIndexedIndirect on render pass and render bundle.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUConst } from '../../../../../constants.js';
+import { kResourceStates } from '../../../../../gpu_test.js';
+import { ValidationTest } from '../../../validation_test.js';
+
+import { kRenderEncodeTypeParams } from './render.js';
+
+const kIndirectDrawTestParams = kRenderEncodeTypeParams.combine('indexed', [true, false]);
+
+class F extends ValidationTest {
+ makeIndexBuffer() {
+ return this.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.INDEX
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('indirect_buffer_state').
+desc(
+ `
+Tests indirect buffer must be valid.
+ `
+).
+paramsSubcasesOnly(kIndirectDrawTestParams.combine('state', kResourceStates)).
+fn((t) => {
+ const { encoderType, indexed, state } = t.params;
+ const pipeline = t.createNoOpRenderPipeline();
+ const indirectBuffer = t.createBufferWithState(state, {
+ size: 256,
+ usage: GPUBufferUsage.INDIRECT
+ });
+
+ const { encoder, validateFinishAndSubmitGivenState } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ if (indexed) {
+ const indexBuffer = t.makeIndexBuffer();
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ encoder.drawIndexedIndirect(indirectBuffer, 0);
+ } else {
+ encoder.drawIndirect(indirectBuffer, 0);
+ }
+
+ validateFinishAndSubmitGivenState(state);
+});
+
+g.test('indirect_buffer,device_mismatch').
+desc(
+ 'Tests draw(Indexed)Indirect cannot be called with an indirect buffer created from another device'
+).
+paramsSubcasesOnly(kIndirectDrawTestParams.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { encoderType, indexed, mismatched } = t.params;
+
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const indirectBuffer = sourceDevice.createBuffer({
+ size: 256,
+ usage: GPUBufferUsage.INDIRECT
+ });
+ t.trackForCleanup(indirectBuffer);
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(t.createNoOpRenderPipeline());
+
+ if (indexed) {
+ encoder.setIndexBuffer(t.makeIndexBuffer(), 'uint32');
+ encoder.drawIndexedIndirect(indirectBuffer, 0);
+ } else {
+ encoder.drawIndirect(indirectBuffer, 0);
+ }
+ validateFinish(!mismatched);
+});
+
+g.test('indirect_buffer_usage').
+desc(
+ `
+Tests indirect buffer must have 'Indirect' usage.
+ `
+).
+paramsSubcasesOnly(
+ kIndirectDrawTestParams.combine('usage', [
+ GPUConst.BufferUsage.INDIRECT, // control case
+ GPUConst.BufferUsage.COPY_DST,
+ GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.INDIRECT]
+ )
+).
+fn((t) => {
+ const { encoderType, indexed, usage } = t.params;
+ const indirectBuffer = t.device.createBuffer({
+ size: 256,
+ usage
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(t.createNoOpRenderPipeline());
+ if (indexed) {
+ const indexBuffer = t.makeIndexBuffer();
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ encoder.drawIndexedIndirect(indirectBuffer, 0);
+ } else {
+ encoder.drawIndirect(indirectBuffer, 0);
+ }
+ validateFinish((usage & GPUBufferUsage.INDIRECT) !== 0);
+});
+
+g.test('indirect_offset_alignment').
+desc(
+ `
+Tests indirect offset must be a multiple of 4.
+ `
+).
+paramsSubcasesOnly(kIndirectDrawTestParams.combine('indirectOffset', [0, 2, 4])).
+fn((t) => {
+ const { encoderType, indexed, indirectOffset } = t.params;
+ const pipeline = t.createNoOpRenderPipeline();
+ const indirectBuffer = t.device.createBuffer({
+ size: 256,
+ usage: GPUBufferUsage.INDIRECT
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ if (indexed) {
+ const indexBuffer = t.makeIndexBuffer();
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ encoder.drawIndexedIndirect(indirectBuffer, indirectOffset);
+ } else {
+ encoder.drawIndirect(indirectBuffer, indirectOffset);
+ }
+
+ validateFinish(indirectOffset % 4 === 0);
+});
+
+g.test('indirect_offset_oob').
+desc(
+ `
+Tests indirect draw calls with various indirect offsets and buffer sizes.
+- (offset, b.size) is
+ - (0, 0)
+ - (0, min size) (control case)
+ - (0, min size + 1) (control case)
+ - (0, min size - 1)
+ - (0, min size - min alignment)
+ - (min alignment, min size + min alignment)
+ - (min alignment, min size + min alignment - 1)
+ - (min alignment / 2, min size + min alignment)
+ - (min alignment +/- 1, min size + min alignment)
+ - (min size, min size)
+ - (min size + min alignment, min size)
+ - min size = indirect draw parameters size
+ - x =(drawIndirect, drawIndexedIndirect)
+ `
+).
+paramsSubcasesOnly(
+ kIndirectDrawTestParams.expandWithParams((p) => {
+ const indirectParamsSize = p.indexed ? 20 : 16;
+ return [
+ { indirectOffset: 0, bufferSize: 0, _valid: false },
+ { indirectOffset: 0, bufferSize: indirectParamsSize, _valid: true },
+ { indirectOffset: 0, bufferSize: indirectParamsSize + 1, _valid: true },
+ { indirectOffset: 0, bufferSize: indirectParamsSize - 1, _valid: false },
+ { indirectOffset: 0, bufferSize: indirectParamsSize - 4, _valid: false },
+ { indirectOffset: 4, bufferSize: indirectParamsSize + 4, _valid: true },
+ { indirectOffset: 4, bufferSize: indirectParamsSize + 3, _valid: false },
+ { indirectOffset: 2, bufferSize: indirectParamsSize + 4, _valid: false },
+ { indirectOffset: 3, bufferSize: indirectParamsSize + 4, _valid: false },
+ { indirectOffset: 5, bufferSize: indirectParamsSize + 4, _valid: false },
+ { indirectOffset: indirectParamsSize, bufferSize: indirectParamsSize, _valid: false },
+ { indirectOffset: indirectParamsSize + 4, bufferSize: indirectParamsSize, _valid: false }];
+
+ })
+).
+fn((t) => {
+ const { encoderType, indexed, indirectOffset, bufferSize, _valid } = t.params;
+ const pipeline = t.createNoOpRenderPipeline();
+ const indirectBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.INDIRECT
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ if (indexed) {
+ const indexBuffer = t.makeIndexBuffer();
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ encoder.drawIndexedIndirect(indirectBuffer, indirectOffset);
+ } else {
+ encoder.drawIndirect(indirectBuffer, indirectOffset);
+ }
+
+ validateFinish(_valid);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/render.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/render.js
new file mode 100644
index 0000000000..bec9355dda
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/render.js
@@ -0,0 +1,29 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kUnitCaseParamsBuilder } from '../../../../../../common/framework/params_builder.js';import { kRenderEncodeTypes } from '../../../../../util/command_buffer_maker.js';
+export const kRenderEncodeTypeParams = kUnitCaseParamsBuilder.combine(
+ 'encoderType',
+ kRenderEncodeTypes
+);
+
+export function buildBufferOffsetAndSizeOOBTestParams(minAlignment, bufferSize) {
+ return kRenderEncodeTypeParams.combineWithParams([
+ // Explicit size
+ { offset: 0, size: 0, _valid: true },
+ { offset: 0, size: 1, _valid: true },
+ { offset: 0, size: 4, _valid: true },
+ { offset: 0, size: 5, _valid: true },
+ { offset: 0, size: bufferSize, _valid: true },
+ { offset: 0, size: bufferSize + 4, _valid: false },
+ { offset: minAlignment, size: bufferSize, _valid: false },
+ { offset: minAlignment, size: bufferSize - minAlignment, _valid: true },
+ { offset: bufferSize - minAlignment, size: minAlignment, _valid: true },
+ { offset: bufferSize, size: 1, _valid: false },
+ // Implicit size: buffer.size - offset
+ { offset: 0, size: undefined, _valid: true },
+ { offset: minAlignment, size: undefined, _valid: true },
+ { offset: bufferSize - minAlignment, size: undefined, _valid: true },
+ { offset: bufferSize, size: undefined, _valid: true },
+ { offset: bufferSize + minAlignment, size: undefined, _valid: false }]
+ );
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setIndexBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setIndexBuffer.spec.js
new file mode 100644
index 0000000000..03eea8fce8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setIndexBuffer.spec.js
@@ -0,0 +1,124 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for setIndexBuffer on render pass and render bundle.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUConst } from '../../../../../constants.js';
+import { kResourceStates } from '../../../../../gpu_test.js';
+import { ValidationTest } from '../../../validation_test.js';
+
+import { kRenderEncodeTypeParams, buildBufferOffsetAndSizeOOBTestParams } from './render.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('index_buffer_state').
+desc(
+ `
+Tests index buffer must be valid.
+ `
+).
+paramsSubcasesOnly(kRenderEncodeTypeParams.combine('state', kResourceStates)).
+fn((t) => {
+ const { encoderType, state } = t.params;
+ const indexBuffer = t.createBufferWithState(state, {
+ size: 16,
+ usage: GPUBufferUsage.INDEX
+ });
+
+ const { encoder, validateFinishAndSubmitGivenState } = t.createEncoder(encoderType);
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ validateFinishAndSubmitGivenState(state);
+});
+
+g.test('index_buffer,device_mismatch').
+desc('Tests setIndexBuffer cannot be called with an index buffer created from another device').
+paramsSubcasesOnly(kRenderEncodeTypeParams.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { encoderType, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const indexBuffer = sourceDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.INDEX
+ });
+ t.trackForCleanup(indexBuffer);
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ validateFinish(!mismatched);
+});
+
+g.test('index_buffer_usage').
+desc(
+ `
+Tests index buffer must have 'Index' usage.
+ `
+).
+paramsSubcasesOnly(
+ kRenderEncodeTypeParams.combine('usage', [
+ GPUConst.BufferUsage.INDEX, // control case
+ GPUConst.BufferUsage.COPY_DST,
+ GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.INDEX]
+ )
+).
+fn((t) => {
+ const { encoderType, usage } = t.params;
+ const indexBuffer = t.device.createBuffer({
+ size: 16,
+ usage
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setIndexBuffer(indexBuffer, 'uint32');
+ validateFinish((usage & GPUBufferUsage.INDEX) !== 0);
+});
+
+g.test('offset_alignment').
+desc(
+ `
+Tests offset must be a multiple of index format’s byte size.
+ `
+).
+paramsSubcasesOnly(
+ kRenderEncodeTypeParams.
+ combine('indexFormat', ['uint16', 'uint32']).
+ expand('offset', (p) => {
+ return p.indexFormat === 'uint16' ? [0, 1, 2] : [0, 2, 4];
+ })
+).
+fn((t) => {
+ const { encoderType, indexFormat, offset } = t.params;
+ const indexBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.INDEX
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setIndexBuffer(indexBuffer, indexFormat, offset);
+
+ const alignment =
+ indexFormat === 'uint16' ? Uint16Array.BYTES_PER_ELEMENT : Uint32Array.BYTES_PER_ELEMENT;
+ validateFinish(offset % alignment === 0);
+});
+
+g.test('offset_and_size_oob').
+desc(
+ `
+Tests offset and size cannot be larger than index buffer size.
+ `
+).
+paramsSubcasesOnly(buildBufferOffsetAndSizeOOBTestParams(4, 256)).
+fn((t) => {
+ const { encoderType, offset, size, _valid } = t.params;
+ const indexBuffer = t.device.createBuffer({
+ size: 256,
+ usage: GPUBufferUsage.INDEX
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setIndexBuffer(indexBuffer, 'uint32', offset, size);
+ validateFinish(_valid);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setPipeline.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setPipeline.spec.js
new file mode 100644
index 0000000000..9f1efd9734
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setPipeline.spec.js
@@ -0,0 +1,62 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for setPipeline on render pass and render bundle.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { kRenderEncodeTypes } from '../../../../../util/command_buffer_maker.js';
+import { ValidationTest } from '../../../validation_test.js';
+
+import { kRenderEncodeTypeParams } from './render.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('invalid_pipeline').
+desc(
+ `
+Tests setPipeline should generate an error iff using an 'invalid' pipeline.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.combine('encoderType', kRenderEncodeTypes).combine('state', ['valid', 'invalid'])
+).
+fn((t) => {
+ const { encoderType, state } = t.params;
+ const pipeline = t.createRenderPipelineWithState(state);
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ validateFinish(state !== 'invalid');
+});
+
+g.test('pipeline,device_mismatch').
+desc('Tests setPipeline cannot be called with a render pipeline created from another device').
+paramsSubcasesOnly(kRenderEncodeTypeParams.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { encoderType, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const pipeline = sourceDevice.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: sourceDevice.createShaderModule({
+ code: `@vertex fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: sourceDevice.createShaderModule({
+ code: '@fragment fn main() {}'
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ validateFinish(!mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setVertexBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setVertexBuffer.spec.js
new file mode 100644
index 0000000000..b5cc30706b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/setVertexBuffer.spec.js
@@ -0,0 +1,144 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for setVertexBuffer on render pass and render bundle.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { makeValueTestVariant } from '../../../../../../common/util/util.js';
+import { GPUConst } from '../../../../../constants.js';
+import { kResourceStates } from '../../../../../gpu_test.js';
+import { ValidationTest } from '../../../validation_test.js';
+
+import { kRenderEncodeTypeParams, buildBufferOffsetAndSizeOOBTestParams } from './render.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('slot').
+desc(
+ `
+Tests slot must be less than the maxVertexBuffers in device limits.
+ `
+).
+paramsSubcasesOnly(
+ kRenderEncodeTypeParams.combine('slotVariant', [
+ { mult: 0, add: 0 },
+ { mult: 1, add: -1 },
+ { mult: 1, add: 0 }]
+ )
+).
+fn((t) => {
+ const { encoderType, slotVariant } = t.params;
+ const maxVertexBuffers = t.device.limits.maxVertexBuffers;
+ const slot = makeValueTestVariant(maxVertexBuffers, slotVariant);
+
+ const vertexBuffer = t.createBufferWithState('valid', {
+ size: 16,
+ usage: GPUBufferUsage.VERTEX
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(slot, vertexBuffer);
+ validateFinish(slot < maxVertexBuffers);
+});
+
+g.test('vertex_buffer_state').
+desc(
+ `
+Tests vertex buffer must be valid.
+ `
+).
+paramsSubcasesOnly(kRenderEncodeTypeParams.combine('state', kResourceStates)).
+fn((t) => {
+ const { encoderType, state } = t.params;
+ const vertexBuffer = t.createBufferWithState(state, {
+ size: 16,
+ usage: GPUBufferUsage.VERTEX
+ });
+
+ const { encoder, validateFinishAndSubmitGivenState } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(0, vertexBuffer);
+ validateFinishAndSubmitGivenState(state);
+});
+
+g.test('vertex_buffer,device_mismatch').
+desc('Tests setVertexBuffer cannot be called with a vertex buffer created from another device').
+paramsSubcasesOnly(kRenderEncodeTypeParams.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { encoderType, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const vertexBuffer = sourceDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.VERTEX
+ });
+ t.trackForCleanup(vertexBuffer);
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(0, vertexBuffer);
+ validateFinish(!mismatched);
+});
+
+g.test('vertex_buffer_usage').
+desc(
+ `
+Tests vertex buffer must have 'Vertex' usage.
+ `
+).
+paramsSubcasesOnly(
+ kRenderEncodeTypeParams.combine('usage', [
+ GPUConst.BufferUsage.VERTEX, // control case
+ GPUConst.BufferUsage.COPY_DST,
+ GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.VERTEX]
+ )
+).
+fn((t) => {
+ const { encoderType, usage } = t.params;
+ const vertexBuffer = t.device.createBuffer({
+ size: 16,
+ usage
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(0, vertexBuffer);
+ validateFinish((usage & GPUBufferUsage.VERTEX) !== 0);
+});
+
+g.test('offset_alignment').
+desc(
+ `
+Tests offset must be a multiple of 4.
+ `
+).
+paramsSubcasesOnly(kRenderEncodeTypeParams.combine('offset', [0, 2, 4])).
+fn((t) => {
+ const { encoderType, offset } = t.params;
+ const vertexBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.VERTEX
+ });
+
+ const { encoder, validateFinish: finish } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(0, vertexBuffer, offset);
+ finish(offset % 4 === 0);
+});
+
+g.test('offset_and_size_oob').
+desc(
+ `
+Tests offset and size cannot be larger than vertex buffer size.
+ `
+).
+paramsSubcasesOnly(buildBufferOffsetAndSizeOOBTestParams(4, 256)).
+fn((t) => {
+ const { encoderType, offset, size, _valid } = t.params;
+ const vertexBuffer = t.device.createBuffer({
+ size: 256,
+ usage: GPUBufferUsage.VERTEX
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(0, vertexBuffer, offset, size);
+ validateFinish(_valid);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.js
new file mode 100644
index 0000000000..8d1e0184e7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render/state_tracking.spec.js
@@ -0,0 +1,184 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for setVertexBuffer/setIndexBuffer state (not validation). See also operation tests.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { range } from '../../../../../../common/util/util.js';
+import { ValidationTest } from '../../../validation_test.js';
+
+class F extends ValidationTest {
+ getVertexBuffer() {
+ return this.device.createBuffer({
+ size: 256,
+ usage: GPUBufferUsage.VERTEX
+ });
+ }
+
+ createRenderPipeline(bufferCount) {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ struct Inputs {
+ ${range(bufferCount, (i) => `\n@location(${i}) a_position${i} : vec3<f32>,`).join('')}
+ };
+ @vertex fn main(input : Inputs
+ ) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ buffers: [
+ {
+ arrayStride: 3 * 4,
+ attributes: range(bufferCount, (i) => ({
+ format: 'float32x3',
+ offset: 0,
+ shaderLocation: i
+ }))
+ }]
+
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+ }
+
+ beginRenderPass(commandEncoder) {
+ const attachmentTexture = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ return commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachmentTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test(`all_needed_vertex_buffer_should_be_bound`).
+desc(
+ `
+In this test we test that any missing vertex buffer for a used slot will cause validation errors when drawing.
+- All (non/indexed, in/direct) draw commands
+ - A needed vertex buffer is not bound
+ - Was bound in another render pass but not the current one
+`
+).
+unimplemented();
+
+g.test(`all_needed_index_buffer_should_be_bound`).
+desc(
+ `
+In this test we test that missing index buffer for a used slot will cause validation errors when drawing.
+- All indexed in/direct draw commands
+ - No index buffer is bound
+`
+).
+unimplemented();
+
+g.test('vertex_buffers_inherit_from_previous_pipeline').fn((t) => {
+ const pipeline1 = t.createRenderPipeline(1);
+ const pipeline2 = t.createRenderPipeline(2);
+
+ const vertexBuffer1 = t.getVertexBuffer();
+ const vertexBuffer2 = t.getVertexBuffer();
+
+ {
+ // Check failure when vertex buffer is not set
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = t.beginRenderPass(commandEncoder);
+ renderPass.setPipeline(pipeline1);
+ renderPass.draw(3);
+ renderPass.end();
+
+ t.expectValidationError(() => {
+ commandEncoder.finish();
+ });
+ }
+ {
+ // Check success when vertex buffer is inherited from previous pipeline
+ const commandEncoder = t.device.createCommandEncoder();
+ const renderPass = t.beginRenderPass(commandEncoder);
+ renderPass.setPipeline(pipeline2);
+ renderPass.setVertexBuffer(0, vertexBuffer1);
+ renderPass.setVertexBuffer(1, vertexBuffer2);
+ renderPass.draw(3);
+ renderPass.setPipeline(pipeline1);
+ renderPass.draw(3);
+ renderPass.end();
+
+ commandEncoder.finish();
+ }
+});
+
+g.test('vertex_buffers_do_not_inherit_between_render_passes').fn((t) => {
+ const pipeline1 = t.createRenderPipeline(1);
+ const pipeline2 = t.createRenderPipeline(2);
+
+ const vertexBuffer1 = t.getVertexBuffer();
+ const vertexBuffer2 = t.getVertexBuffer();
+
+ {
+ // Check success when vertex buffer is set for each render pass
+ const commandEncoder = t.device.createCommandEncoder();
+ {
+ const renderPass = t.beginRenderPass(commandEncoder);
+ renderPass.setPipeline(pipeline2);
+ renderPass.setVertexBuffer(0, vertexBuffer1);
+ renderPass.setVertexBuffer(1, vertexBuffer2);
+ renderPass.draw(3);
+ renderPass.end();
+ }
+ {
+ const renderPass = t.beginRenderPass(commandEncoder);
+ renderPass.setPipeline(pipeline1);
+ renderPass.setVertexBuffer(0, vertexBuffer1);
+ renderPass.draw(3);
+ renderPass.end();
+ }
+ commandEncoder.finish();
+ }
+ {
+ // Check failure because vertex buffer is not inherited in second subpass
+ const commandEncoder = t.device.createCommandEncoder();
+ {
+ const renderPass = t.beginRenderPass(commandEncoder);
+ renderPass.setPipeline(pipeline2);
+ renderPass.setVertexBuffer(0, vertexBuffer1);
+ renderPass.setVertexBuffer(1, vertexBuffer2);
+ renderPass.draw(3);
+ renderPass.end();
+ }
+ {
+ const renderPass = t.beginRenderPass(commandEncoder);
+ renderPass.setPipeline(pipeline1);
+ renderPass.draw(3);
+ renderPass.end();
+ }
+
+ t.expectValidationError(() => {
+ commandEncoder.finish();
+ });
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render_pass.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render_pass.spec.js
new file mode 100644
index 0000000000..5374001bfb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/render_pass.spec.js
@@ -0,0 +1,14 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for render pass encoding.
+Does **not** test usage scopes (resource_usages/), GPUProgrammablePassEncoder (programmable_pass),
+dynamic state (dynamic_render_state.spec.ts), or GPURenderEncoderBase (render.spec.ts).
+
+TODO:
+- executeBundles:
+ - with {zero, one, multiple} bundles where {zero, one} of them are invalid objects
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/setBindGroup.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/setBindGroup.spec.js
new file mode 100644
index 0000000000..bbf30240d1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/cmds/setBindGroup.spec.js
@@ -0,0 +1,435 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+setBindGroup validation tests.
+
+TODO: merge these notes and implement.
+> (Note: If there are errors with using certain binding types in certain passes, test those in the file for that pass type, not here.)
+>
+> - state tracking (probably separate file)
+> - x= {compute pass, render pass}
+> - {null, compatible, incompatible} current pipeline (should have no effect without draw/dispatch)
+> - setBindGroup in different orders (e.g. 0,1,2 vs 2,0,1)
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { makeValueTestVariant, range, unreachable } from '../../../../../common/util/util.js';
+import {
+ kBufferBindingTypes,
+ kMinDynamicBufferOffsetAlignment } from
+'../../../../capability_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import {
+ kProgrammableEncoderTypes } from
+
+'../../../../util/command_buffer_maker.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ encoderTypeToStageFlag(encoderType) {
+ switch (encoderType) {
+ case 'compute pass':
+ return GPUShaderStage.COMPUTE;
+ case 'render pass':
+ case 'render bundle':
+ return GPUShaderStage.FRAGMENT;
+ default:
+ unreachable('Unknown encoder type');
+ }
+ }
+
+ createBindingResourceWithState(
+ resourceType,
+ state)
+ {
+ switch (resourceType) {
+ case 'texture':{
+ const texture = this.createTextureWithState('valid');
+ const view = texture.createView();
+ if (state === 'destroyed') {
+ texture.destroy();
+ }
+ return view;
+ }
+ case 'buffer':
+ return {
+ buffer: this.createBufferWithState(state, {
+ size: 4,
+ usage: GPUBufferUsage.STORAGE
+ })
+ };
+ default:
+ unreachable('unknown resource type');
+ }
+ }
+
+ /**
+ * If state is 'invalid', creates an invalid bind group with valid resources.
+ * If state is 'destroyed', creates a valid bind group with destroyed resources.
+ */
+ createBindGroup(
+ state,
+ resourceType,
+ encoderType,
+ indices)
+ {
+ if (state === 'invalid') {
+ this.device.pushErrorScope('validation');
+ indices = new Array(indices.length + 1).fill(0);
+ }
+
+ const layout = this.device.createBindGroupLayout({
+ entries: indices.map((binding) => ({
+ binding,
+ visibility: this.encoderTypeToStageFlag(encoderType),
+ ...(resourceType === 'buffer' ? { buffer: { type: 'storage' } } : { texture: {} })
+ }))
+ });
+ const bindGroup = this.device.createBindGroup({
+ layout,
+ entries: indices.map((binding) => ({
+ binding,
+ resource: this.createBindingResourceWithState(
+ resourceType,
+ state === 'destroyed' ? state : 'valid'
+ )
+ }))
+ });
+
+ if (state === 'invalid') {
+ void this.device.popErrorScope();
+ }
+ return bindGroup;
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('state_and_binding_index').
+desc('Tests that setBindGroup correctly handles {valid, invalid, destroyed} bindGroups.').
+params((u) =>
+u.
+combine('encoderType', kProgrammableEncoderTypes).
+combine('state', kResourceStates).
+combine('resourceType', ['buffer', 'texture'])
+).
+fn((t) => {
+ const { encoderType, state, resourceType } = t.params;
+ const maxBindGroups = t.device.limits.maxBindGroups;
+
+ function runTest(index) {
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType);
+ encoder.setBindGroup(index, t.createBindGroup(state, resourceType, encoderType, [index]));
+
+ validateFinishAndSubmit(state !== 'invalid' && index < maxBindGroups, state !== 'destroyed');
+ }
+
+ // MAINTENANCE_TODO: move to subcases() once we can query the device limits
+ for (const index of [1, maxBindGroups - 1, maxBindGroups]) {
+ t.debug(`test bind group index ${index}`);
+ runTest(index);
+ }
+});
+
+g.test('bind_group,device_mismatch').
+desc(
+ `
+ Tests setBindGroup cannot be called with a bind group created from another device
+ - x= setBindGroup {sequence overload, Uint32Array overload}
+ `
+).
+params((u) =>
+u.
+combine('encoderType', kProgrammableEncoderTypes).
+beginSubcases().
+combine('useU32Array', [true, false]).
+combine('mismatched', [true, false])
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { encoderType, useU32Array, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const buffer = sourceDevice.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ const layout = sourceDevice.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: t.encoderTypeToStageFlag(encoderType),
+ buffer: { type: 'storage', hasDynamicOffset: useU32Array }
+ }]
+
+ });
+
+ const bindGroup = sourceDevice.createBindGroup({
+ layout,
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer }
+ }]
+
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ if (useU32Array) {
+ encoder.setBindGroup(0, bindGroup, new Uint32Array([0]), 0, 1);
+ } else {
+ encoder.setBindGroup(0, bindGroup);
+ }
+ validateFinish(!mismatched);
+});
+
+g.test('dynamic_offsets_passed_but_not_expected').
+desc('Tests that setBindGroup correctly errors on unexpected dynamicOffsets.').
+params((u) => u.combine('encoderType', kProgrammableEncoderTypes)).
+fn((t) => {
+ const { encoderType } = t.params;
+ const bindGroup = t.createBindGroup('valid', 'buffer', encoderType, []);
+ const dynamicOffsets = [0];
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setBindGroup(0, bindGroup, dynamicOffsets);
+ validateFinish(false);
+});
+
+g.test('dynamic_offsets_match_expectations_in_pass_encoder').
+desc('Tests that given dynamicOffsets match the specified bindGroup.').
+params((u) =>
+u.
+combine('encoderType', kProgrammableEncoderTypes).
+combineWithParams([
+{ dynamicOffsets: [256, 0], _success: true }, // Dynamic offsets aligned
+{ dynamicOffsets: [1, 2], _success: false }, // Dynamic offsets not aligned
+
+// Wrong number of dynamic offsets
+{ dynamicOffsets: [256, 0, 0], _success: false },
+{ dynamicOffsets: [256], _success: false },
+{ dynamicOffsets: [], _success: false },
+
+// Dynamic uniform buffer out of bounds because of binding size
+{ dynamicOffsets: [512, 0], _success: false },
+{ dynamicOffsets: [1024, 0], _success: false },
+{ dynamicOffsets: [0xffffffff, 0], _success: false },
+
+// Dynamic storage buffer out of bounds because of binding size
+{ dynamicOffsets: [0, 512], _success: false },
+{ dynamicOffsets: [0, 1024], _success: false },
+{ dynamicOffsets: [0, 0xffffffff], _success: false }]
+).
+combine('useU32array', [false, true])
+).
+fn((t) => {
+ const kBindingSize = 12;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
+ buffer: {
+ type: 'uniform',
+ hasDynamicOffset: true
+ }
+ },
+ {
+ binding: 1,
+ visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
+ buffer: {
+ type: 'storage',
+ hasDynamicOffset: true
+ }
+ }]
+
+ });
+
+ const uniformBuffer = t.device.createBuffer({
+ size: 2 * kMinDynamicBufferOffsetAlignment + 8,
+ usage: GPUBufferUsage.UNIFORM
+ });
+
+ const storageBuffer = t.device.createBuffer({
+ size: 2 * kMinDynamicBufferOffsetAlignment + 8,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: uniformBuffer,
+ size: kBindingSize
+ }
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: storageBuffer,
+ size: kBindingSize
+ }
+ }]
+
+ });
+
+ const { encoderType, dynamicOffsets, useU32array, _success } = t.params;
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ if (useU32array) {
+ encoder.setBindGroup(0, bindGroup, new Uint32Array(dynamicOffsets), 0, dynamicOffsets.length);
+ } else {
+ encoder.setBindGroup(0, bindGroup, dynamicOffsets);
+ }
+ validateFinish(_success);
+});
+
+g.test('u32array_start_and_length').
+desc('Tests that dynamicOffsetsData(Start|Length) apply to the given Uint32Array.').
+paramsSubcasesOnly([
+// dynamicOffsetsDataLength > offsets.length
+{
+ offsets: [0],
+ dynamicOffsetsDataStart: 0,
+ dynamicOffsetsDataLength: 2,
+ _success: false
+},
+// dynamicOffsetsDataStart + dynamicOffsetsDataLength > offsets.length
+{
+ offsets: [0],
+ dynamicOffsetsDataStart: 1,
+ dynamicOffsetsDataLength: 1,
+ _success: false
+},
+{
+ offsets: [0, 0],
+ dynamicOffsetsDataStart: 1,
+ dynamicOffsetsDataLength: 1,
+ _success: true
+},
+{
+ offsets: [0, 0, 0],
+ dynamicOffsetsDataStart: 1,
+ dynamicOffsetsDataLength: 1,
+ _success: true
+},
+{
+ offsets: [0, 0],
+ dynamicOffsetsDataStart: 0,
+ dynamicOffsetsDataLength: 2,
+ _success: true
+}]
+).
+fn((t) => {
+ const { offsets, dynamicOffsetsDataStart, dynamicOffsetsDataLength, _success } = t.params;
+ const kBindingSize = 8;
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: range(dynamicOffsetsDataLength, (i) => ({
+ binding: i,
+ visibility: GPUShaderStage.FRAGMENT,
+ buffer: {
+ type: 'storage',
+ hasDynamicOffset: true
+ }
+ }))
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: range(dynamicOffsetsDataLength, (i) => ({
+ binding: i,
+ resource: {
+ buffer: t.createBufferWithState('valid', {
+ size: kBindingSize,
+ usage: GPUBufferUsage.STORAGE
+ }),
+ size: kBindingSize
+ }
+ }))
+ });
+
+ const { encoder, validateFinish } = t.createEncoder('render pass');
+
+ const doSetBindGroup = () => {
+ encoder.setBindGroup(
+ 0,
+ bindGroup,
+ new Uint32Array(offsets),
+ dynamicOffsetsDataStart,
+ dynamicOffsetsDataLength
+ );
+ };
+
+ if (_success) {
+ doSetBindGroup();
+ } else {
+ t.shouldThrow('RangeError', doSetBindGroup);
+ }
+
+ // RangeError in setBindGroup does not cause the encoder to become invalid.
+ validateFinish(true);
+});
+
+g.test('buffer_dynamic_offsets').
+desc(
+ `
+ Test that the dynamic offsets of the BufferLayout is a multiple of
+ 'minUniformBufferOffsetAlignment|minStorageBufferOffsetAlignment' if the BindGroup entry defines
+ buffer and the buffer type is 'uniform|storage|read-only-storage'.
+ `
+).
+params((u) =>
+u //
+.combine('type', kBufferBindingTypes).
+combine('encoderType', kProgrammableEncoderTypes).
+beginSubcases().
+combine('dynamicOffsetVariant', [
+{ mult: 1, add: 0 },
+{ mult: 0.5, add: 0 },
+{ mult: 1.5, add: 0 },
+{ mult: 2, add: 0 },
+{ mult: 1, add: 2 }]
+)
+).
+fn((t) => {
+ const { type, dynamicOffsetVariant, encoderType } = t.params;
+ const kBindingSize = 12;
+
+ const minAlignment =
+ t.device.limits[
+ type === 'uniform' ? 'minUniformBufferOffsetAlignment' : 'minStorageBufferOffsetAlignment'];
+
+ const dynamicOffset = makeValueTestVariant(minAlignment, dynamicOffsetVariant);
+
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type, hasDynamicOffset: true }
+ }]
+
+ });
+
+ const usage = type === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE;
+ const isValid = dynamicOffset % minAlignment === 0;
+
+ const buffer = t.device.createBuffer({
+ size: 3 * kMinDynamicBufferOffsetAlignment,
+ usage
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ entries: [{ binding: 0, resource: { buffer, size: kBindingSize } }],
+ layout: bindGroupLayout
+ });
+
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setBindGroup(0, bindGroup, [dynamicOffset]);
+ validateFinish(isValid);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.js
new file mode 100644
index 0000000000..b78322cbff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.js
@@ -0,0 +1,259 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+createRenderBundleEncoder validation tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import { kMaxColorAttachmentsToTest } from '../../../capability_info.js';
+import {
+ computeBytesPerSampleFromFormats,
+ kAllTextureFormats,
+ kDepthStencilFormats,
+ kTextureFormatInfo,
+ kRenderableColorTextureFormats } from
+'../../../format_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('attachment_state,limits,maxColorAttachments').
+desc(`Tests that attachment state must have <= device.limits.maxColorAttachments.`).
+params((u) =>
+u.beginSubcases().combine(
+ 'colorFormatCount',
+ range(kMaxColorAttachmentsToTest, (i) => i + 1)
+)
+).
+fn((t) => {
+ const { colorFormatCount } = t.params;
+ const maxColorAttachments = t.device.limits.maxColorAttachments;
+ t.skipIf(
+ colorFormatCount > maxColorAttachments,
+ `${colorFormatCount} > maxColorAttachments: ${maxColorAttachments}`
+ );
+ t.expectValidationError(() => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: Array(colorFormatCount).fill('r8unorm')
+ });
+ }, colorFormatCount > t.device.limits.maxColorAttachments);
+});
+
+g.test('attachment_state,limits,maxColorAttachmentBytesPerSample,aligned').
+desc(
+ `
+ Tests that the total color attachment bytes per sample <=
+ device.limits.maxColorAttachmentBytesPerSample when using the same format (aligned) for multiple
+ attachments.
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+beginSubcases().
+combine(
+ 'colorFormatCount',
+ range(kMaxColorAttachmentsToTest, (i) => i + 1)
+)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn((t) => {
+ const { format, colorFormatCount } = t.params;
+ const maxColorAttachments = t.device.limits.maxColorAttachments;
+ t.skipIf(
+ colorFormatCount > maxColorAttachments,
+ `${colorFormatCount} > maxColorAttachments: ${maxColorAttachments}`
+ );
+ const info = kTextureFormatInfo[format];
+ const shouldError =
+ !info.colorRender ||
+ info.colorRender.byteCost * colorFormatCount >
+ t.device.limits.maxColorAttachmentBytesPerSample;
+
+ t.expectValidationError(() => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: Array(colorFormatCount).fill(format)
+ });
+ }, shouldError);
+});
+
+g.test('attachment_state,limits,maxColorAttachmentBytesPerSample,unaligned').
+desc(
+ `
+ Tests that the total color attachment bytes per sample <=
+ device.limits.maxColorAttachmentBytesPerSample when using various sets of (potentially)
+ unaligned formats.
+ `
+).
+params((u) =>
+u.combineWithParams([
+// Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though
+// 1+4+8+16+1 < 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm
+// 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']
+
+},
+{
+ formats: [
+ 'r32float',
+ 'rgba8unorm',
+ 'rgba32float',
+ 'r8unorm',
+ 'r8unorm']
+
+}]
+)
+).
+fn((t) => {
+ const { formats } = t.params;
+
+ t.skipIf(
+ formats.length > t.device.limits.maxColorAttachments,
+ `numColorAttachments: ${formats.length} > maxColorAttachments: ${t.device.limits.maxColorAttachments}`
+ );
+
+ const shouldError =
+ computeBytesPerSampleFromFormats(formats) > t.device.limits.maxColorAttachmentBytesPerSample;
+
+ t.expectValidationError(() => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: formats
+ });
+ }, shouldError);
+});
+
+g.test('attachment_state,empty_color_formats').
+desc(`Tests that if no colorFormats are given, a depthStencilFormat must be specified.`).
+params((u) =>
+u.beginSubcases().combine('depthStencilFormat', [undefined, 'depth24plus-stencil8'])
+).
+fn((t) => {
+ const { depthStencilFormat } = t.params;
+ t.expectValidationError(() => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: [],
+ depthStencilFormat
+ });
+ }, depthStencilFormat === undefined);
+});
+
+g.test('valid_texture_formats').
+desc(
+ `
+ Tests that createRenderBundleEncoder only accepts valid formats for its attachments.
+ - colorFormats
+ - depthStencilFormat
+ `
+).
+params((u) =>
+u //
+.combine('format', kAllTextureFormats).
+beginSubcases().
+combine('attachment', ['color', 'depthStencil'])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(format);
+}).
+fn((t) => {
+ const { format, attachment } = t.params;
+
+ const colorRenderable = kTextureFormatInfo[format].colorRender;
+
+ const depthStencil = kTextureFormatInfo[format].depth || kTextureFormatInfo[format].stencil;
+
+ switch (attachment) {
+ case 'color':{
+ t.expectValidationError(() => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: [format]
+ });
+ }, !colorRenderable);
+
+ break;
+ }
+ case 'depthStencil':{
+ t.expectValidationError(() => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: [],
+ depthStencilFormat: format
+ });
+ }, !depthStencil);
+
+ break;
+ }
+ }
+});
+
+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
+ `
+).
+params((u) =>
+u //
+.combine('depthStencilFormat', kDepthStencilFormats).
+beginSubcases().
+combine('depthReadOnly', [false, true]).
+combine('stencilReadOnly', [false, true])
+).
+beforeAllSubcases((t) => {
+ const { depthStencilFormat } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(depthStencilFormat);
+}).
+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'],
+ depthReadOnly,
+ stencilReadOnly
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_open_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_open_state.spec.js
new file mode 100644
index 0000000000..663db39fa1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_open_state.spec.js
@@ -0,0 +1,587 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests to all commands of GPUCommandEncoder, GPUComputePassEncoder, and
+GPURenderPassEncoder when the encoder is not finished.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { unreachable } from '../../../../common/util/util.js';
+import { ValidationTest } from '../validation_test.js';
+
+import { beginRenderPassWithQuerySet } from './queries/common.js';
+
+class F extends ValidationTest {
+ createRenderPipelineForTest() {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `@fragment fn main() {}`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ }
+ });
+ }
+
+ createBindGroupForTest() {
+ return this.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: this.device.createSampler()
+ }],
+
+ layout: this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ sampler: { type: 'filtering' }
+ }]
+
+ })
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+
+const kEncoderCommandInfo =
+
+{
+ beginComputePass: {},
+ beginRenderPass: {},
+ clearBuffer: {},
+ copyBufferToBuffer: {},
+ copyBufferToTexture: {},
+ copyTextureToBuffer: {},
+ copyTextureToTexture: {},
+ insertDebugMarker: {},
+ popDebugGroup: {},
+ pushDebugGroup: {},
+ writeTimestamp: {},
+ resolveQuerySet: {}
+};
+const kEncoderCommands = keysOf(kEncoderCommandInfo);
+
+
+const kRenderPassEncoderCommandInfo =
+
+{
+ draw: {},
+ drawIndexed: {},
+ drawIndexedIndirect: {},
+ drawIndirect: {},
+ setIndexBuffer: {},
+ setBindGroup: {},
+ setVertexBuffer: {},
+ setPipeline: {},
+ setViewport: {},
+ setScissorRect: {},
+ setBlendConstant: {},
+ setStencilReference: {},
+ beginOcclusionQuery: {},
+ endOcclusionQuery: {},
+ executeBundles: {},
+ pushDebugGroup: {},
+ popDebugGroup: {},
+ insertDebugMarker: {}
+};
+const kRenderPassEncoderCommands = keysOf(kRenderPassEncoderCommandInfo);
+
+
+
+
+
+const kRenderBundleEncoderCommandInfo =
+
+{
+ draw: {},
+ drawIndexed: {},
+ drawIndexedIndirect: {},
+ drawIndirect: {},
+ setPipeline: {},
+ setBindGroup: {},
+ setIndexBuffer: {},
+ setVertexBuffer: {},
+ pushDebugGroup: {},
+ popDebugGroup: {},
+ insertDebugMarker: {}
+};
+const kRenderBundleEncoderCommands = keysOf(kRenderBundleEncoderCommandInfo);
+
+// MAINTENANCE_TODO: remove the deprecated 'dispatch' and 'dispatchIndirect' here once they're
+// removed from `@webgpu/types`.
+
+
+
+
+const kComputePassEncoderCommandInfo =
+
+{
+ setBindGroup: {},
+ setPipeline: {},
+ dispatchWorkgroups: {},
+ dispatchWorkgroupsIndirect: {},
+ pushDebugGroup: {},
+ popDebugGroup: {},
+ insertDebugMarker: {}
+};
+const kComputePassEncoderCommands = keysOf(kComputePassEncoderCommandInfo);
+
+g.test('non_pass_commands').
+desc(
+ `
+ Test that functions of GPUCommandEncoder generate a validation error if the encoder is already
+ finished.
+ `
+).
+params((u) =>
+u.
+combine('command', kEncoderCommands).
+beginSubcases().
+combine('finishBeforeCommand', [false, true])
+).
+beforeAllSubcases((t) => {
+ switch (t.params.command) {
+ case 'writeTimestamp':
+ t.selectDeviceOrSkipTestCase('timestamp-query');
+ break;
+ }
+}).
+fn((t) => {
+ const { command, finishBeforeCommand } = t.params;
+
+ const srcBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+ const dstBuffer = t.device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.QUERY_RESOLVE
+ });
+
+ const textureSize = { width: 1, height: 1 };
+ const textureFormat = 'rgba8unorm';
+ const srcTexture = t.device.createTexture({
+ size: textureSize,
+ format: textureFormat,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ const dstTexture = t.device.createTexture({
+ size: textureSize,
+ format: textureFormat,
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ const querySet = t.device.createQuerySet({
+ type: command === 'writeTimestamp' ? 'timestamp' : 'occlusion',
+ count: 1
+ });
+
+ const encoder = t.device.createCommandEncoder();
+
+ if (finishBeforeCommand) encoder.finish();
+
+ t.expectValidationError(() => {
+ switch (command) {
+ case 'beginComputePass':
+ {
+ encoder.beginComputePass();
+ }
+ break;
+ case 'beginRenderPass':
+ {
+ encoder.beginRenderPass({ colorAttachments: [] });
+ }
+ break;
+ case 'clearBuffer':
+ {
+ encoder.clearBuffer(dstBuffer, 0, 16);
+ }
+ break;
+ case 'copyBufferToBuffer':
+ {
+ encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, 0);
+ }
+ break;
+ case 'copyBufferToTexture':
+ {
+ encoder.copyBufferToTexture(
+ { buffer: srcBuffer },
+ { texture: dstTexture },
+ textureSize
+ );
+ }
+ break;
+ case 'copyTextureToBuffer':
+ {
+ encoder.copyTextureToBuffer(
+ { texture: srcTexture },
+ { buffer: dstBuffer },
+ textureSize
+ );
+ }
+ break;
+ case 'copyTextureToTexture':
+ {
+ encoder.copyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ textureSize
+ );
+ }
+ break;
+ case 'insertDebugMarker':
+ {
+ encoder.insertDebugMarker('marker');
+ }
+ break;
+ case 'pushDebugGroup':
+ {
+ encoder.pushDebugGroup('group');
+ }
+ break;
+ case 'popDebugGroup':
+ {
+ encoder.popDebugGroup();
+ }
+ break;
+ case 'writeTimestamp':
+ {
+ encoder.writeTimestamp(querySet, 0);
+ }
+ break;
+ case 'resolveQuerySet':
+ {
+ encoder.resolveQuerySet(querySet, 0, 1, dstBuffer, 0);
+ }
+ break;
+ default:
+ unreachable();
+ }
+ }, finishBeforeCommand);
+});
+
+g.test('render_pass_commands').
+desc(
+ `
+ Test that functions of GPURenderPassEncoder generate a validation error if the encoder or the
+ pass is already finished.
+
+ - TODO: Consider testing: nothing before command, end before command, end+finish before command.
+ `
+).
+params((u) =>
+u.
+combine('command', kRenderPassEncoderCommands).
+beginSubcases().
+combine('finishBeforeCommand', [false, true])
+).
+fn((t) => {
+ const { command, finishBeforeCommand } = t.params;
+
+ const querySet = t.device.createQuerySet({ type: 'occlusion', count: 1 });
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = beginRenderPassWithQuerySet(t, encoder, querySet);
+
+ const buffer = t.device.createBuffer({
+ size: 12,
+ usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.VERTEX
+ });
+
+ const pipeline = t.createRenderPipelineForTest();
+
+ const bindGroup = t.createBindGroupForTest();
+
+ if (finishBeforeCommand) {
+ renderPass.end();
+ encoder.finish();
+ }
+
+ t.expectValidationError(() => {
+ switch (command) {
+ case 'draw':
+ {
+ renderPass.draw(1);
+ }
+ break;
+ case 'drawIndexed':
+ {
+ renderPass.drawIndexed(1);
+ }
+ break;
+ case 'drawIndirect':
+ {
+ renderPass.drawIndirect(buffer, 1);
+ }
+ break;
+ case 'setIndexBuffer':
+ {
+ renderPass.setIndexBuffer(buffer, 'uint32');
+ }
+ break;
+ case 'drawIndexedIndirect':
+ {
+ renderPass.drawIndexedIndirect(buffer, 0);
+ }
+ break;
+ case 'setBindGroup':
+ {
+ renderPass.setBindGroup(0, bindGroup);
+ }
+ break;
+ case 'setVertexBuffer':
+ {
+ renderPass.setVertexBuffer(1, buffer);
+ }
+ break;
+ case 'setPipeline':
+ {
+ renderPass.setPipeline(pipeline);
+ }
+ break;
+ case 'setViewport':
+ {
+ const kNumTestPoints = 8;
+ const kViewportMinDepth = 0;
+ const kViewportMaxDepth = 1;
+ renderPass.setViewport(0, 0, kNumTestPoints, 0, kViewportMinDepth, kViewportMaxDepth);
+ }
+ break;
+ case 'setScissorRect':
+ {
+ renderPass.setScissorRect(0, 0, 0, 0);
+ }
+ break;
+ case 'setBlendConstant':
+ {
+ renderPass.setBlendConstant({ r: 1.0, g: 1.0, b: 1.0, a: 1.0 });
+ }
+ break;
+ case 'setStencilReference':
+ {
+ renderPass.setStencilReference(0);
+ }
+ break;
+ case 'beginOcclusionQuery':
+ {
+ renderPass.beginOcclusionQuery(0);
+ }
+ break;
+ case 'endOcclusionQuery':
+ {
+ renderPass.endOcclusionQuery();
+ }
+ break;
+ case 'executeBundles':
+ {
+ renderPass.executeBundles([]);
+ }
+ break;
+ case 'pushDebugGroup':
+ {
+ encoder.pushDebugGroup('group');
+ }
+ break;
+ case 'popDebugGroup':
+ {
+ encoder.popDebugGroup();
+ }
+ break;
+ case 'insertDebugMarker':
+ {
+ encoder.insertDebugMarker('marker');
+ }
+ break;
+ default:
+ unreachable();
+ }
+ }, finishBeforeCommand);
+});
+
+g.test('render_bundle_commands').
+desc(
+ `
+ Test that functions of GPURenderBundleEncoder generate a validation error if the encoder or the
+ pass is already finished.
+ `
+).
+params((u) =>
+u.
+combine('command', kRenderBundleEncoderCommands).
+beginSubcases().
+combine('finishBeforeCommand', [false, true])
+).
+fn((t) => {
+ const { command, finishBeforeCommand } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 12,
+ usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.VERTEX
+ });
+
+ const pipeline = t.createRenderPipelineForTest();
+
+ const bindGroup = t.createBindGroupForTest();
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ });
+
+ if (finishBeforeCommand) {
+ bundleEncoder.finish();
+ }
+
+ t.expectValidationError(() => {
+ switch (command) {
+ case 'draw':
+ {
+ bundleEncoder.draw(1);
+ }
+ break;
+ case 'drawIndexed':
+ {
+ bundleEncoder.drawIndexed(1);
+ }
+ break;
+ case 'drawIndexedIndirect':
+ {
+ bundleEncoder.drawIndexedIndirect(buffer, 0);
+ }
+ break;
+ case 'drawIndirect':
+ {
+ bundleEncoder.drawIndirect(buffer, 1);
+ }
+ break;
+ case 'setPipeline':
+ {
+ bundleEncoder.setPipeline(pipeline);
+ }
+ break;
+ case 'setBindGroup':
+ {
+ bundleEncoder.setBindGroup(0, bindGroup);
+ }
+ break;
+ case 'setIndexBuffer':
+ {
+ bundleEncoder.setIndexBuffer(buffer, 'uint32');
+ }
+ break;
+ case 'setVertexBuffer':
+ {
+ bundleEncoder.setVertexBuffer(1, buffer);
+ }
+ break;
+ case 'pushDebugGroup':
+ {
+ bundleEncoder.pushDebugGroup('group');
+ }
+ break;
+ case 'popDebugGroup':
+ {
+ bundleEncoder.popDebugGroup();
+ }
+ break;
+ case 'insertDebugMarker':
+ {
+ bundleEncoder.insertDebugMarker('marker');
+ }
+ break;
+ default:
+ unreachable();
+ }
+ }, finishBeforeCommand);
+});
+
+g.test('compute_pass_commands').
+desc(
+ `
+ Test that functions of GPUComputePassEncoder generate a validation error if the encoder or the
+ pass is already finished.
+
+ - TODO: Consider testing: nothing before command, end before command, end+finish before command.
+ `
+).
+params((u) =>
+u.
+combine('command', kComputePassEncoderCommands).
+beginSubcases().
+combine('finishBeforeCommand', [false, true])
+).
+fn((t) => {
+ const { command, finishBeforeCommand } = t.params;
+
+ const encoder = t.device.createCommandEncoder();
+ const computePass = encoder.beginComputePass();
+
+ const indirectBuffer = t.device.createBuffer({
+ size: 12,
+ usage: GPUBufferUsage.INDIRECT
+ });
+
+ const computePipeline = t.createNoOpComputePipeline();
+
+ const bindGroup = t.createBindGroupForTest();
+
+ if (finishBeforeCommand) {
+ computePass.end();
+ encoder.finish();
+ }
+
+ t.expectValidationError(() => {
+ switch (command) {
+ case 'setBindGroup':
+ {
+ computePass.setBindGroup(0, bindGroup);
+ }
+ break;
+ case 'setPipeline':
+ {
+ computePass.setPipeline(computePipeline);
+ }
+ break;
+ case 'dispatchWorkgroups':
+ {
+ computePass.dispatchWorkgroups(0);
+ }
+ break;
+ case 'dispatchWorkgroupsIndirect':
+ {
+ computePass.dispatchWorkgroupsIndirect(indirectBuffer, 0);
+ }
+ break;
+ case 'pushDebugGroup':
+ {
+ computePass.pushDebugGroup('group');
+ }
+ break;
+ case 'popDebugGroup':
+ {
+ computePass.popDebugGroup();
+ }
+ break;
+ case 'insertDebugMarker':
+ {
+ computePass.insertDebugMarker('marker');
+ }
+ break;
+ default:
+ unreachable();
+ }
+ }, finishBeforeCommand);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_state.spec.js
new file mode 100644
index 0000000000..eec8feb8f3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/encoder_state.spec.js
@@ -0,0 +1,250 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+TODO:
+- createCommandEncoder
+- non-pass command, or beginPass, during {render, compute} pass
+- {before (control case), after} finish()
+ - x= {finish(), ... all non-pass commands}
+- {before (control case), after} end()
+ - x= {render, compute} pass
+ - x= {finish(), ... all relevant pass commands}
+ - x= {
+ - before endPass (control case)
+ - after endPass (no pass open)
+ - after endPass+beginPass (a new pass of the same type is open)
+ - }
+ - should make whole encoder invalid
+- ?
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { objectEquals } from '../../../../common/util/util.js';
+import { ValidationTest } from '../validation_test.js';
+
+class F extends ValidationTest {
+ beginRenderPass(commandEncoder, view) {
+ return commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+
+ createAttachmentTextureView() {
+ const texture = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ this.trackForCleanup(texture);
+ return texture.createView();
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('pass_end_invalid_order').
+desc(
+ `
+ Test that beginning a {compute,render} pass before ending the previous {compute,render} pass
+ causes an error.
+ `
+).
+params((u) =>
+u.
+combine('pass0Type', ['compute', 'render']).
+combine('pass1Type', ['compute', 'render']).
+beginSubcases().
+combine('firstPassEnd', [true, false]).
+combine('endPasses', [[], [0], [1], [0, 1], [1, 0]])
+// Don't end the first pass multiple times (that generates a validation error but doesn't invalidate the encoder)
+.unless((p) => p.firstPassEnd && p.endPasses.includes(0))
+).
+fn((t) => {
+ const { pass0Type, pass1Type, firstPassEnd, endPasses } = t.params;
+
+ const view = t.createAttachmentTextureView();
+ const encoder = t.device.createCommandEncoder();
+
+ const firstPass =
+ pass0Type === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder, view);
+
+ if (firstPassEnd) firstPass.end();
+
+ // Begin a second pass before ending the previous pass.
+ const secondPass =
+ pass1Type === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder, view);
+
+ const passes = [firstPass, secondPass];
+ for (const index of endPasses) {
+ passes[index].end();
+ }
+
+ // If {endPasses} is '[1]' and {firstPass} ends, it's a control case.
+ const valid = firstPassEnd && objectEquals(endPasses, [1]);
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !valid);
+});
+
+g.test('call_after_successful_finish').
+desc(`Test that encoding command after a successful finish generates a validation error.`).
+params((u) =>
+u.
+combine('callCmd', ['beginComputePass', 'beginRenderPass', 'insertDebugMarker']).
+beginSubcases().
+combine('prePassType', ['compute', 'render', 'no-op']).
+combine('IsEncoderFinished', [false, true])
+).
+fn((t) => {
+ const { prePassType, IsEncoderFinished, callCmd } = t.params;
+
+ const view = t.createAttachmentTextureView();
+ const encoder = t.device.createCommandEncoder();
+
+ if (prePassType !== 'no-op') {
+ const pass =
+ prePassType === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder, view);
+ pass.end();
+ }
+
+ if (IsEncoderFinished) {
+ encoder.finish();
+ }
+
+ switch (callCmd) {
+ case 'beginComputePass':
+ {
+ let pass;
+ t.expectValidationError(() => {
+ pass = encoder.beginComputePass();
+ }, IsEncoderFinished);
+ t.expectValidationError(() => {
+ pass.end();
+ }, IsEncoderFinished);
+ }
+ break;
+ case 'beginRenderPass':
+ {
+ let pass;
+ t.expectValidationError(() => {
+ pass = t.beginRenderPass(encoder, view);
+ }, IsEncoderFinished);
+ t.expectValidationError(() => {
+ pass.end();
+ }, IsEncoderFinished);
+ }
+ break;
+ case 'insertDebugMarker':
+ t.expectValidationError(() => {
+ encoder.insertDebugMarker('');
+ }, IsEncoderFinished);
+ break;
+ }
+
+ if (!IsEncoderFinished) {
+ encoder.finish();
+ }
+});
+
+g.test('pass_end_none').
+desc(
+ `
+ Test that ending a {compute,render} pass without ending the passes generates a validation error.
+ `
+).
+paramsSubcasesOnly((u) => u.combine('passType', ['compute', 'render']).combine('endCount', [0, 1])).
+fn((t) => {
+ const { passType, endCount } = t.params;
+
+ const view = t.createAttachmentTextureView();
+ const encoder = t.device.createCommandEncoder();
+
+ const pass =
+ passType === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder, view);
+
+ for (let i = 0; i < endCount; ++i) {
+ pass.end();
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, endCount === 0);
+});
+
+g.test('pass_end_twice,basic').
+desc(
+ 'Test that ending a {compute,render} pass twice generates a validation error. The parent encoder (command encoder) can be either locked or open.'
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('passType', ['compute', 'render'])
+// Simply end twice, the parent encoder is open at that time. If the second pass end is in the middle of another pass, the parent encoder is locked. It should generate a validation error in either situation.
+.combine('endTwice', [false, true]).
+combine('secondEndInAnotherPass', [false, 'compute', 'render']).
+filter((p) => p.endTwice || !p.secondEndInAnotherPass)
+).
+fn((t) => {
+ const { passType, endTwice, secondEndInAnotherPass } = t.params;
+
+ const view = t.createAttachmentTextureView();
+ const encoder = t.device.createCommandEncoder();
+
+ const pass =
+ passType === 'compute' ? encoder.beginComputePass() : t.beginRenderPass(encoder, view);
+
+ pass.end();
+
+ if (secondEndInAnotherPass) {
+ const pass1 =
+ secondEndInAnotherPass === 'compute' ?
+ encoder.beginComputePass() :
+ t.beginRenderPass(encoder, view);
+
+ t.expectValidationError(() => {
+ pass.end();
+ });
+
+ pass1.end();
+ } else {
+ if (endTwice) {
+ t.expectValidationError(() => {
+ pass.end();
+ });
+ }
+ }
+
+ encoder.finish();
+});
+
+g.test('pass_end_twice,render_pass_invalid').
+desc(
+ 'Test that ending a render pass twice generates a validation error even if the pass is invalid.'
+).
+paramsSubcasesOnly((u) => u.combine('endTwice', [false, true])).
+fn((t) => {
+ const { endTwice } = t.params;
+
+ const encoder = t.device.createCommandEncoder();
+ // Pass encoder creation will fail because both color and depth/stencil attachments are empty.
+ const pass = encoder.beginRenderPass({
+ colorAttachments: []
+ });
+
+ pass.end();
+
+ if (endTwice) {
+ t.expectValidationError(() => {
+ pass.end();
+ });
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js
new file mode 100644
index 0000000000..cb15b694de
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js
@@ -0,0 +1,777 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+TODO:
+- test compatibility between bind groups and pipelines
+ - the binding resource in bindGroups[i].layout is "group-equivalent" (value-equal) to pipelineLayout.bgls[i].
+ - in the test fn, test once without the dispatch/draw (should always be valid) and once with
+ the dispatch/draw, to make sure the validation happens in dispatch/draw.
+ - x= {dispatch, all draws} (dispatch/draw should be size 0 to make sure validation still happens if no-op)
+ - x= all relevant stages
+
+TODO: subsume existing test, rewrite fixture as needed.
+TODO: Add externalTexture to kResourceTypes [1]
+`;import { kUnitCaseParamsBuilder } from '../../../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { memcpy, unreachable } from '../../../../../common/util/util.js';
+import {
+ kSamplerBindingTypes,
+ kShaderStageCombinations,
+ kBufferBindingTypes } from
+
+'../../../../capability_info.js';
+import { GPUConst } from '../../../../constants.js';
+import {
+
+ kProgrammableEncoderTypes } from
+'../../../../util/command_buffer_maker.js';
+import { ValidationTest } from '../../validation_test.js';
+
+const kComputeCmds = ['dispatch', 'dispatchIndirect'];
+
+const kRenderCmds = ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'];
+
+
+// Test resource type compatibility in pipeline and bind group
+// [1]: Need to add externalTexture
+const kResourceTypes = [
+'uniformBuf',
+'filtSamp',
+'sampledTex',
+'storageTex'];
+
+
+function getTestCmds(
+encoderType)
+{
+ return encoderType === 'compute pass' ? kComputeCmds : kRenderCmds;
+}
+
+const kCompatTestParams = kUnitCaseParamsBuilder.
+combine('encoderType', kProgrammableEncoderTypes).
+expand('call', (p) => getTestCmds(p.encoderType)).
+combine('callWithZero', [true, false]);
+
+class F extends ValidationTest {
+ getIndexBuffer() {
+ return this.device.createBuffer({
+ size: 8 * Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.INDEX
+ });
+ }
+
+ getIndirectBuffer(indirectParams) {
+ const buffer = this.device.createBuffer({
+ mappedAtCreation: true,
+ size: indirectParams.length * Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_DST
+ });
+ memcpy({ src: new Uint32Array(indirectParams) }, { dst: buffer.getMappedRange() });
+ buffer.unmap();
+ return buffer;
+ }
+
+ getBindingResourceType(entry) {
+ if (entry.buffer !== undefined) return 'uniformBuf';
+ if (entry.sampler !== undefined) return 'filtSamp';
+ if (entry.texture !== undefined) return 'sampledTex';
+ if (entry.storageTexture !== undefined) return 'storageTex';
+ unreachable();
+ }
+
+ createRenderPipelineWithLayout(
+ bindGroups)
+ {
+ const shader = `
+ @vertex fn vs_main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(1.0, 1.0, 0.0, 1.0);
+ }
+
+ @fragment fn fs_main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }
+ `;
+ const module = this.device.createShaderModule({ code: shader });
+ const pipeline = this.device.createRenderPipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: bindGroups.map((entries) => this.device.createBindGroupLayout({ entries }))
+ }),
+ vertex: {
+ module,
+ entryPoint: 'vs_main'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs_main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+ return pipeline;
+ }
+
+ createComputePipelineWithLayout(
+ bindGroups)
+ {
+ const shader = `
+ @compute @workgroup_size(1)
+ fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
+ }
+ `;
+
+ const module = this.device.createShaderModule({ code: shader });
+ const pipeline = this.device.createComputePipeline({
+ layout: this.device.createPipelineLayout({
+ bindGroupLayouts: bindGroups.map((entries) => this.device.createBindGroupLayout({ entries }))
+ }),
+ compute: {
+ module,
+ entryPoint: 'main'
+ }
+ });
+ return pipeline;
+ }
+
+ createBindGroupWithLayout(bglEntries) {
+ const bgEntries = [];
+ for (const entry of bglEntries) {
+ const resource = this.getBindingResource(this.getBindingResourceType(entry));
+ bgEntries.push({
+ binding: entry.binding,
+ resource
+ });
+ }
+
+ return this.device.createBindGroup({
+ entries: bgEntries,
+ layout: this.device.createBindGroupLayout({ entries: bglEntries })
+ });
+ }
+
+ doCompute(pass, call, callWithZero) {
+ const x = callWithZero ? 0 : 1;
+ switch (call) {
+ case 'dispatch':
+ pass.dispatchWorkgroups(x, 1, 1);
+ break;
+ case 'dispatchIndirect':
+ pass.dispatchWorkgroupsIndirect(this.getIndirectBuffer([x, 1, 1]), 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ doRender(
+ pass,
+ call,
+ callWithZero)
+ {
+ const vertexCount = callWithZero ? 0 : 3;
+ switch (call) {
+ case 'draw':
+ pass.draw(vertexCount, 1, 0, 0);
+ break;
+ case 'drawIndexed':
+ pass.setIndexBuffer(this.getIndexBuffer(), 'uint32');
+ pass.drawIndexed(vertexCount, 1, 0, 0, 0);
+ break;
+ case 'drawIndirect':
+ pass.drawIndirect(this.getIndirectBuffer([vertexCount, 1, 0, 0, 0]), 0);
+ break;
+ case 'drawIndexedIndirect':
+ pass.setIndexBuffer(this.getIndexBuffer(), 'uint32');
+ pass.drawIndexedIndirect(this.getIndirectBuffer([vertexCount, 1, 0, 0, 0]), 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ createBindGroupLayoutEntry(
+ encoderType,
+ resourceType,
+ useU32Array)
+ {
+ const entry = {
+ binding: 0,
+ visibility: encoderType === 'compute pass' ? GPUShaderStage.COMPUTE : GPUShaderStage.FRAGMENT
+ };
+
+ switch (resourceType) {
+ case 'uniformBuf':
+ entry.buffer = { hasDynamicOffset: useU32Array }; // default type: uniform
+ break;
+ case 'filtSamp':
+ entry.sampler = {}; // default type: filtering
+ break;
+ case 'sampledTex':
+ entry.texture = {}; // default sampleType: float
+ break;
+ case 'storageTex':
+ entry.storageTexture = { access: 'write-only', format: 'rgba8unorm' };
+ break;
+ }
+
+ return entry;
+ }
+
+ runTest(
+ encoderType,
+ pipeline,
+ bindGroups,
+ dynamicOffsets,
+ call,
+ callWithZero,
+ success)
+ {
+ const { encoder, validateFinish } = this.createEncoder(encoderType);
+
+ if (encoder instanceof GPUComputePassEncoder) {
+ encoder.setPipeline(pipeline);
+ } else {
+ encoder.setPipeline(pipeline);
+ }
+
+ for (let i = 0; i < bindGroups.length; i++) {
+ const bindGroup = bindGroups[i];
+ if (!bindGroup) {
+ break;
+ }
+ if (dynamicOffsets) {
+ encoder.setBindGroup(
+ i,
+ bindGroup,
+ new Uint32Array(dynamicOffsets),
+ 0,
+ dynamicOffsets.length
+ );
+ } else {
+ encoder.setBindGroup(i, bindGroup);
+ }
+ }
+
+ if (encoder instanceof GPUComputePassEncoder) {
+ this.doCompute(encoder, call, callWithZero);
+ } else {
+ this.doRender(encoder, call, callWithZero);
+ }
+
+ validateFinish(success);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('bind_groups_and_pipeline_layout_mismatch').
+desc(
+ `
+ Tests the bind groups must match the requirements of the pipeline layout.
+ - bind groups required by the pipeline layout are required.
+ - bind groups unused by the pipeline layout can be set or not.
+ `
+).
+params(
+ kCompatTestParams.
+ beginSubcases().
+ combineWithParams([
+ { setBindGroup0: true, setBindGroup1: true, setUnusedBindGroup2: true, _success: true },
+ { setBindGroup0: true, setBindGroup1: true, setUnusedBindGroup2: false, _success: true },
+ { setBindGroup0: true, setBindGroup1: false, setUnusedBindGroup2: true, _success: false },
+ { setBindGroup0: false, setBindGroup1: true, setUnusedBindGroup2: true, _success: false },
+ { setBindGroup0: false, setBindGroup1: false, setUnusedBindGroup2: false, _success: false }]
+ ).
+ combine('useU32Array', [false, true])
+).
+fn((t) => {
+ const {
+ encoderType,
+ call,
+ callWithZero,
+ setBindGroup0,
+ setBindGroup1,
+ setUnusedBindGroup2,
+ _success,
+ useU32Array
+ } = t.params;
+ const visibility =
+ encoderType === 'compute pass' ? GPUShaderStage.COMPUTE : GPUShaderStage.VERTEX;
+
+ const bindGroupLayouts = [
+ // bind group layout 0
+ [
+ {
+ binding: 0,
+ visibility,
+ buffer: { hasDynamicOffset: useU32Array } // default type: uniform
+ }],
+
+ // bind group layout 1
+ [
+ {
+ binding: 0,
+ visibility,
+ buffer: { hasDynamicOffset: useU32Array } // default type: uniform
+ }]];
+
+
+
+ // Create required bind groups
+ const bindGroup0 = setBindGroup0 ? t.createBindGroupWithLayout(bindGroupLayouts[0]) : undefined;
+ const bindGroup1 = setBindGroup1 ? t.createBindGroupWithLayout(bindGroupLayouts[1]) : undefined;
+ const unusedBindGroup2 = setUnusedBindGroup2 ?
+ t.createBindGroupWithLayout(bindGroupLayouts[1]) :
+ undefined;
+
+ // Create fixed pipeline
+ const pipeline =
+ encoderType === 'compute pass' ?
+ t.createComputePipelineWithLayout(bindGroupLayouts) :
+ t.createRenderPipelineWithLayout(bindGroupLayouts);
+
+ const dynamicOffsets = useU32Array ? [0] : undefined;
+
+ // Test without the dispatch/draw (should always be valid)
+ t.runTest(
+ encoderType,
+ pipeline,
+ [bindGroup0, bindGroup1, unusedBindGroup2],
+ dynamicOffsets,
+ undefined,
+ false,
+ true
+ );
+
+ // Test with the dispatch/draw, to make sure the validation happens in dispatch/draw.
+ t.runTest(
+ encoderType,
+ pipeline,
+ [bindGroup0, bindGroup1, unusedBindGroup2],
+ dynamicOffsets,
+ call,
+ callWithZero,
+ _success
+ );
+});
+
+g.test('buffer_binding,render_pipeline').
+desc(
+ `
+ The GPUBufferBindingLayout bindings configure should be exactly
+ same in PipelineLayout and bindgroup.
+ - TODO: test more draw functions, e.g. indirect
+ - TODO: test more visibilities, e.g. vertex
+ - TODO: bind group should be created with different layout
+ `
+).
+params((u) => u.combine('type', kBufferBindingTypes)).
+fn((t) => {
+ const { type } = t.params;
+
+ // Create fixed bindGroup
+ const uniformBuffer = t.getUniformBuffer();
+
+ const bindGroup = t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: uniformBuffer
+ }
+ }],
+
+ layout: t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ buffer: {} // default type: uniform
+ }]
+
+ })
+ });
+
+ // Create pipeline with different layouts
+ const pipeline = t.createRenderPipelineWithLayout([
+ [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ buffer: {
+ type
+ }
+ }]]
+
+ );
+
+ const { encoder, validateFinish } = t.createEncoder('render pass');
+ encoder.setPipeline(pipeline);
+ encoder.setBindGroup(0, bindGroup);
+ encoder.draw(3);
+
+ validateFinish(type === undefined || type === 'uniform');
+});
+
+g.test('sampler_binding,render_pipeline').
+desc(
+ `
+ The GPUSamplerBindingLayout bindings configure should be exactly
+ same in PipelineLayout and bindgroup.
+ - TODO: test more draw functions, e.g. indirect
+ - TODO: test more visibilities, e.g. vertex
+ `
+).
+params((u) =>
+u //
+.combine('bglType', kSamplerBindingTypes).
+combine('bgType', kSamplerBindingTypes)
+).
+fn((t) => {
+ const { bglType, bgType } = t.params;
+ const bindGroup = t.device.createBindGroup({
+ entries: [
+ {
+ binding: 0,
+ resource:
+ bgType === 'comparison' ?
+ t.device.createSampler({ compare: 'always' }) :
+ t.device.createSampler()
+ }],
+
+ layout: t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ sampler: { type: bgType }
+ }]
+
+ })
+ });
+
+ // Create pipeline with different layouts
+ const pipeline = t.createRenderPipelineWithLayout([
+ [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ sampler: {
+ type: bglType
+ }
+ }]]
+
+ );
+
+ const { encoder, validateFinish } = t.createEncoder('render pass');
+ encoder.setPipeline(pipeline);
+ encoder.setBindGroup(0, bindGroup);
+ encoder.draw(3);
+
+ validateFinish(bglType === bgType);
+});
+
+g.test('bgl_binding_mismatch').
+desc(
+ 'Tests the binding number must exist or not exist in both bindGroups[i].layout and pipelineLayout.bgls[i]'
+).
+params(
+ kCompatTestParams.
+ beginSubcases().
+ combineWithParams([
+ { bgBindings: [0, 1, 2], plBindings: [0, 1, 2], _success: true },
+ { bgBindings: [0, 1, 2], plBindings: [0, 1, 3], _success: false },
+ { bgBindings: [0, 2], plBindings: [0, 2], _success: true },
+ { bgBindings: [0, 2], plBindings: [2, 0], _success: true },
+ { bgBindings: [0, 1, 2], plBindings: [0, 1], _success: false },
+ { bgBindings: [0, 1], plBindings: [0, 1, 2], _success: false }]
+ ).
+ combine('useU32Array', [false, true])
+).
+fn((t) => {
+ const { encoderType, call, callWithZero, bgBindings, plBindings, _success, useU32Array } =
+ t.params;
+ const visibility =
+ encoderType === 'compute pass' ? GPUShaderStage.COMPUTE : GPUShaderStage.VERTEX;
+
+ const bglEntries = [];
+ for (const binding of bgBindings) {
+ bglEntries.push({
+ binding,
+ visibility,
+ buffer: { hasDynamicOffset: useU32Array } // default type: uniform
+ });
+ }
+ const bindGroup = t.createBindGroupWithLayout(bglEntries);
+
+ const plEntries = [[]];
+ for (const binding of plBindings) {
+ plEntries[0].push({
+ binding,
+ visibility,
+ buffer: { hasDynamicOffset: useU32Array } // default type: uniform
+ });
+ }
+ const pipeline =
+ encoderType === 'compute pass' ?
+ t.createComputePipelineWithLayout(plEntries) :
+ t.createRenderPipelineWithLayout(plEntries);
+
+ const dynamicOffsets = useU32Array ? new Array(bgBindings.length).fill(0) : undefined;
+
+ // Test without the dispatch/draw (should always be valid)
+ t.runTest(encoderType, pipeline, [bindGroup], dynamicOffsets, undefined, false, true);
+
+ // Test with the dispatch/draw, to make sure the validation happens in dispatch/draw.
+ t.runTest(encoderType, pipeline, [bindGroup], dynamicOffsets, call, callWithZero, _success);
+});
+
+g.test('bgl_visibility_mismatch').
+desc('Tests the visibility in bindGroups[i].layout and pipelineLayout.bgls[i] must be matched').
+params(
+ kCompatTestParams.
+ beginSubcases().
+ combine('bgVisibility', kShaderStageCombinations).
+ expand('plVisibility', (p) =>
+ p.encoderType === 'compute pass' ?
+ [GPUConst.ShaderStage.COMPUTE] :
+ [
+ GPUConst.ShaderStage.VERTEX,
+ GPUConst.ShaderStage.FRAGMENT,
+ GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT]
+
+ ).
+ combine('useU32Array', [false, true])
+).
+fn((t) => {
+ const { encoderType, call, callWithZero, bgVisibility, plVisibility, useU32Array } = t.params;
+
+ const bglEntries = [
+ {
+ binding: 0,
+ visibility: bgVisibility,
+ buffer: { hasDynamicOffset: useU32Array } // default type: uniform
+ }];
+
+ const bindGroup = t.createBindGroupWithLayout(bglEntries);
+
+ const plEntries = [
+ [
+ {
+ binding: 0,
+ visibility: plVisibility,
+ buffer: { hasDynamicOffset: useU32Array } // default type: uniform
+ }]];
+
+
+ const pipeline =
+ encoderType === 'compute pass' ?
+ t.createComputePipelineWithLayout(plEntries) :
+ t.createRenderPipelineWithLayout(plEntries);
+
+ const dynamicOffsets = useU32Array ? [0] : undefined;
+
+ // Test without the dispatch/draw (should always be valid)
+ t.runTest(encoderType, pipeline, [bindGroup], dynamicOffsets, undefined, false, true);
+
+ // Test with the dispatch/draw, to make sure the validation happens in dispatch/draw.
+ t.runTest(
+ encoderType,
+ pipeline,
+ [bindGroup],
+ dynamicOffsets,
+ call,
+ callWithZero,
+ bgVisibility === plVisibility
+ );
+});
+
+g.test('bgl_resource_type_mismatch').
+desc(
+ `
+ Tests the binding resource type in bindGroups[i].layout and pipelineLayout.bgls[i] must be matched
+ - TODO: Test externalTexture
+ `
+).
+params(
+ kCompatTestParams.
+ beginSubcases().
+ combine('bgResourceType', kResourceTypes).
+ combine('plResourceType', kResourceTypes).
+ expand('useU32Array', (p) => p.bgResourceType === 'uniformBuf' ? [true, false] : [false])
+).
+fn((t) => {
+ const { encoderType, call, callWithZero, bgResourceType, plResourceType, useU32Array } =
+ t.params;
+
+ const bglEntries = [
+ t.createBindGroupLayoutEntry(encoderType, bgResourceType, useU32Array)];
+
+ const bindGroup = t.createBindGroupWithLayout(bglEntries);
+
+ const plEntries = [
+ [t.createBindGroupLayoutEntry(encoderType, plResourceType, useU32Array)]];
+
+ const pipeline =
+ encoderType === 'compute pass' ?
+ t.createComputePipelineWithLayout(plEntries) :
+ t.createRenderPipelineWithLayout(plEntries);
+
+ const dynamicOffsets = useU32Array ? [0] : undefined;
+
+ // Test without the dispatch/draw (should always be valid)
+ t.runTest(encoderType, pipeline, [bindGroup], dynamicOffsets, undefined, false, true);
+
+ // Test with the dispatch/draw, to make sure the validation happens in dispatch/draw.
+ t.runTest(
+ encoderType,
+ pipeline,
+ [bindGroup],
+ dynamicOffsets,
+ call,
+ callWithZero,
+ bgResourceType === plResourceType
+ );
+});
+
+g.test('empty_bind_group_layouts_requires_empty_bind_groups,compute_pass').
+desc(
+ `
+ Test that a compute pipeline with empty bind groups layouts requires empty bind groups to be set.
+ `
+).
+params((u) =>
+u.
+combine('bindGroupLayoutEntryCount', [3, 4]).
+combine('computeCommand', ['dispatchIndirect', 'dispatch'])
+).
+fn((t) => {
+ const { bindGroupLayoutEntryCount, computeCommand } = t.params;
+
+ const emptyBGLCount = 4;
+ const emptyBGL = t.device.createBindGroupLayout({ entries: [] });
+ const emptyBGLs = [];
+ for (let i = 0; i < emptyBGLCount; i++) {
+ emptyBGLs.push(emptyBGL);
+ }
+
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: emptyBGLs
+ });
+
+ const pipeline = t.device.createComputePipeline({
+ layout: pipelineLayout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: '@compute @workgroup_size(1) fn main() {}'
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const emptyBindGroup = t.device.createBindGroup({
+ layout: emptyBGL,
+ entries: []
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const computePass = encoder.beginComputePass();
+ computePass.setPipeline(pipeline);
+ for (let i = 0; i < bindGroupLayoutEntryCount; i++) {
+ computePass.setBindGroup(i, emptyBindGroup);
+ }
+
+ t.doCompute(computePass, computeCommand, true);
+ computePass.end();
+
+ const success = bindGroupLayoutEntryCount === emptyBGLCount;
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('empty_bind_group_layouts_requires_empty_bind_groups,render_pass').
+desc(
+ `
+ Test that a render pipeline with empty bind groups layouts requires empty bind groups to be set.
+ `
+).
+params((u) =>
+u.
+combine('bindGroupLayoutEntryCount', [3, 4]).
+combine('renderCommand', [
+'draw',
+'drawIndexed',
+'drawIndirect',
+'drawIndexedIndirect']
+)
+).
+fn((t) => {
+ const { bindGroupLayoutEntryCount, renderCommand } = t.params;
+
+ const emptyBGLCount = 4;
+ const emptyBGL = t.device.createBindGroupLayout({ entries: [] });
+ const emptyBGLs = [];
+ for (let i = 0; i < emptyBGLCount; i++) {
+ emptyBGLs.push(emptyBGL);
+ }
+
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: emptyBGLs
+ });
+
+ const colorFormat = 'rgba8unorm';
+ const pipeline = t.device.createRenderPipeline({
+ layout: pipelineLayout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `@vertex fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() {}`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorFormat, writeMask: 0 }]
+ }
+ });
+
+ const emptyBindGroup = t.device.createBindGroup({
+ layout: emptyBGL,
+ entries: []
+ });
+
+ const encoder = t.device.createCommandEncoder();
+
+ const attachmentTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachmentTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+
+ renderPass.setPipeline(pipeline);
+ for (let i = 0; i < bindGroupLayoutEntryCount; i++) {
+ renderPass.setBindGroup(i, emptyBindGroup);
+ }
+ t.doRender(renderPass, renderCommand, true);
+ renderPass.end();
+
+ const success = bindGroupLayoutEntryCount === emptyBGLCount;
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/begin_end.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/begin_end.spec.js
new file mode 100644
index 0000000000..0fd90049d1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/begin_end.spec.js
@@ -0,0 +1,117 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation for encoding begin/endable queries.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../validation_test.js';
+
+import { beginRenderPassWithQuerySet, createQuerySetWithType } from './common.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('occlusion_query,begin_end_balance').
+desc(
+ `
+Tests that begin/end occlusion queries mismatch on render pass:
+- begin n queries, then end m queries, for various n and m.
+ `
+).
+paramsSubcasesOnly([
+{ begin: 0, end: 1 },
+{ begin: 1, end: 0 },
+{ begin: 1, end: 1 }, // control case
+{ begin: 1, end: 2 },
+{ begin: 2, end: 1 }]
+).
+fn((t) => {
+ const { begin, end } = t.params;
+
+ const occlusionQuerySet = createQuerySetWithType(t, 'occlusion', 2);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet });
+ for (let i = 0; i < begin; i++) {
+ encoder.encoder.beginOcclusionQuery(i);
+ }
+ for (let j = 0; j < end; j++) {
+ encoder.encoder.endOcclusionQuery();
+ }
+ encoder.validateFinishAndSubmit(begin === end, true);
+});
+
+g.test('occlusion_query,begin_end_invalid_nesting').
+desc(
+ `
+Tests the invalid nesting of begin/end occlusion queries:
+- begin index 0, end, begin index 0, end (control case)
+- begin index 0, begin index 0, end, end
+- begin index 0, begin index 1, end, end
+ `
+).
+paramsSubcasesOnly([
+{ calls: [0, 'end', 1, 'end'], _valid: true }, // control case
+{ calls: [0, 0, 'end', 'end'], _valid: false },
+{ calls: [0, 1, 'end', 'end'], _valid: false }]
+).
+fn((t) => {
+ const { calls, _valid } = t.params;
+
+ const occlusionQuerySet = createQuerySetWithType(t, 'occlusion', 2);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet });
+ for (const i of calls) {
+ if (i !== 'end') {
+ encoder.encoder.beginOcclusionQuery(i);
+ } else {
+ encoder.encoder.endOcclusionQuery();
+ }
+ }
+ encoder.validateFinishAndSubmit(_valid, true);
+});
+
+g.test('occlusion_query,disjoint_queries_with_same_query_index').
+desc(
+ `
+Tests that two disjoint occlusion queries cannot be begun with same query index on same render pass:
+- begin index 0, end, begin index 0, end
+- call on {same (invalid), different (control case)} render pass
+ `
+).
+paramsSubcasesOnly((u) => u.combine('isOnSameRenderPass', [false, true])).
+fn((t) => {
+ const querySet = createQuerySetWithType(t, 'occlusion', 1);
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = beginRenderPassWithQuerySet(t, encoder, querySet);
+ pass.beginOcclusionQuery(0);
+ pass.endOcclusionQuery();
+
+ if (t.params.isOnSameRenderPass) {
+ pass.beginOcclusionQuery(0);
+ pass.endOcclusionQuery();
+ pass.end();
+ } else {
+ pass.end();
+ const otherPass = beginRenderPassWithQuerySet(t, encoder, querySet);
+ otherPass.beginOcclusionQuery(0);
+ otherPass.endOcclusionQuery();
+ otherPass.end();
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, t.params.isOnSameRenderPass);
+});
+
+g.test('nesting').
+desc(
+ `
+Tests that whether it's allowed to nest various types of queries:
+- call {occlusion, timestamp} query in same type or other type.
+ `
+).
+paramsSubcasesOnly([
+{ begin: 'occlusion', nest: 'timestamp', end: 'occlusion', _valid: true },
+{ begin: 'occlusion', nest: 'occlusion', end: 'occlusion', _valid: false },
+{ begin: 'timestamp', nest: 'occlusion', end: 'occlusion', _valid: true }]
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/common.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/common.js
new file mode 100644
index 0000000000..bd14a51024
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/common.js
@@ -0,0 +1,37 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export function createQuerySetWithType(
+t,
+type,
+count)
+{
+ return t.device.createQuerySet({
+ type,
+ count
+ });
+}
+
+export function beginRenderPassWithQuerySet(
+t,
+encoder,
+querySet)
+{
+ const view = t.device.
+ createTexture({
+ format: 'rgba8unorm',
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ }).
+ createView();
+ return encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ occlusionQuerySet: querySet
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/general.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/general.spec.js
new file mode 100644
index 0000000000..4d7342c990
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/general.spec.js
@@ -0,0 +1,152 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation for encoding queries.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kQueryTypes } from '../../../../capability_info.js';
+import { ValidationTest } from '../../validation_test.js';
+
+import { createQuerySetWithType } from './common.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('occlusion_query,query_type').
+desc(
+ `
+Tests that set occlusion query set with all types in render pass descriptor:
+- type {occlusion (control case), timestamp}
+- {undefined} for occlusion query set in render pass descriptor
+ `
+).
+params((u) => u.combine('type', [undefined, ...kQueryTypes])).
+beforeAllSubcases((t) => {
+ const { type } = t.params;
+ if (type) {
+ t.selectDeviceForQueryTypeOrSkipTestCase(type);
+ }
+}).
+fn((t) => {
+ const type = t.params.type;
+ const querySet = type === undefined ? undefined : createQuerySetWithType(t, type, 1);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet: querySet });
+ encoder.encoder.beginOcclusionQuery(0);
+ encoder.encoder.endOcclusionQuery();
+ encoder.validateFinish(type === 'occlusion');
+});
+
+g.test('occlusion_query,invalid_query_set').
+desc(
+ `
+Tests that begin occlusion query with a invalid query set that failed during creation.
+ `
+).
+paramsSubcasesOnly((u) => u.combine('querySetState', ['valid', 'invalid'])).
+fn((t) => {
+ const occlusionQuerySet = t.createQuerySetWithState(t.params.querySetState);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet });
+ encoder.encoder.beginOcclusionQuery(0);
+ encoder.encoder.endOcclusionQuery();
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+});
+
+g.test('occlusion_query,query_index').
+desc(
+ `
+Tests that begin occlusion query with query index:
+- queryIndex {in, out of} range for GPUQuerySet
+ `
+).
+paramsSubcasesOnly((u) => u.combine('queryIndex', [0, 2])).
+fn((t) => {
+ const occlusionQuerySet = createQuerySetWithType(t, 'occlusion', 2);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet });
+ encoder.encoder.beginOcclusionQuery(t.params.queryIndex);
+ encoder.encoder.endOcclusionQuery();
+ encoder.validateFinish(t.params.queryIndex < 2);
+});
+
+g.test('timestamp_query,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
+ `
+).
+params((u) =>
+u.
+combine('type', kQueryTypes).
+beginSubcases().
+expand('queryIndex', (p) => p.type === 'timestamp' ? [0, 2] : [0])
+).
+beforeAllSubcases((t) => {
+ const { type } = t.params;
+
+ // writeTimestamp is only available for devices that enable the 'timestamp-query' feature.
+ const queryTypes = ['timestamp'];
+ if (type !== 'timestamp') {
+ queryTypes.push(type);
+ }
+
+ t.selectDeviceForQueryTypeOrSkipTestCase(queryTypes);
+}).
+fn((t) => {
+ const { type, queryIndex } = t.params;
+
+ const count = 2;
+ const querySet = createQuerySetWithType(t, type, count);
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.writeTimestamp(querySet, queryIndex);
+ encoder.validateFinish(type === 'timestamp' && queryIndex < count);
+});
+
+g.test('timestamp_query,invalid_query_set').
+desc(
+ `
+Tests that write timestamp to a invalid query set that failed during creation:
+- x= {non-pass} encoder
+ `
+).
+paramsSubcasesOnly((u) => u.combine('querySetState', ['valid', 'invalid'])).
+beforeAllSubcases((t) => {
+ t.selectDeviceForQueryTypeOrSkipTestCase('timestamp');
+}).
+fn((t) => {
+ const { querySetState } = t.params;
+
+ const querySet = t.createQuerySetWithState(querySetState, {
+ type: 'timestamp',
+ count: 2
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.writeTimestamp(querySet, 0);
+ encoder.validateFinish(querySetState !== 'invalid');
+});
+
+g.test('timestamp_query,device_mismatch').
+desc('Tests writeTimestamp cannot be called with a query set created from another device').
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectDeviceForQueryTypeOrSkipTestCase('timestamp');
+ t.selectMismatchedDeviceOrSkipTestCase('timestamp-query');
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const querySet = sourceDevice.createQuerySet({
+ type: 'timestamp',
+ count: 2
+ });
+ t.trackForCleanup(querySet);
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.writeTimestamp(querySet, 0);
+ encoder.validateFinish(!mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/resolveQuerySet.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/resolveQuerySet.spec.js
new file mode 100644
index 0000000000..d0b19e4e04
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/queries/resolveQuerySet.spec.js
@@ -0,0 +1,181 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for resolveQuerySet.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';import { GPUConst } from '../../../../constants.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+export const kQueryCount = 2;
+
+g.test('queryset_and_destination_buffer_state').
+desc(
+ `
+Tests that resolve query set must be with valid query set and destination buffer.
+- {invalid, destroyed} GPUQuerySet results in validation error.
+- {invalid, destroyed} destination buffer results in validation error.
+ `
+).
+params((u) =>
+u //
+.combine('querySetState', kResourceStates).
+combine('destinationState', kResourceStates)
+).
+fn((t) => {
+ const { querySetState, destinationState } = t.params;
+
+ const shouldBeValid = querySetState !== 'invalid' && destinationState !== 'invalid';
+ const shouldSubmitSuccess = querySetState === 'valid' && destinationState === 'valid';
+
+ const querySet = t.createQuerySetWithState(querySetState);
+
+ const destination = t.createBufferWithState(destinationState, {
+ size: kQueryCount * 8,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, 0, 1, destination, 0);
+ encoder.validateFinishAndSubmit(shouldBeValid, shouldSubmitSuccess);
+});
+
+g.test('first_query_and_query_count').
+desc(
+ `
+Tests that resolve query set with invalid firstQuery and queryCount:
+- firstQuery and/or queryCount out of range
+ `
+).
+paramsSubcasesOnly([
+{ firstQuery: 0, queryCount: kQueryCount }, // control case
+{ firstQuery: 0, queryCount: kQueryCount + 1 },
+{ firstQuery: 1, queryCount: kQueryCount },
+{ firstQuery: kQueryCount, queryCount: 1 }]
+).
+fn((t) => {
+ const { firstQuery, queryCount } = t.params;
+
+ const querySet = t.device.createQuerySet({ type: 'occlusion', count: kQueryCount });
+ const destination = t.device.createBuffer({
+ size: kQueryCount * 8,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, firstQuery, queryCount, destination, 0);
+ encoder.validateFinish(firstQuery + queryCount <= kQueryCount);
+});
+
+g.test('destination_buffer_usage').
+desc(
+ `
+Tests that resolve query set with invalid destinationBuffer:
+- Buffer usage {with, without} QUERY_RESOLVE
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('bufferUsage', [
+GPUConst.BufferUsage.STORAGE,
+GPUConst.BufferUsage.QUERY_RESOLVE // control case
+])
+).
+fn((t) => {
+ const querySet = t.device.createQuerySet({ type: 'occlusion', count: kQueryCount });
+ const destination = t.device.createBuffer({
+ size: kQueryCount * 8,
+ usage: t.params.bufferUsage
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, 0, kQueryCount, destination, 0);
+ encoder.validateFinish(t.params.bufferUsage === GPUConst.BufferUsage.QUERY_RESOLVE);
+});
+
+g.test('destination_offset_alignment').
+desc(
+ `
+Tests that resolve query set with invalid destinationOffset:
+- destinationOffset is not a multiple of 256
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destinationOffset', [0, 128, 256, 384])).
+fn((t) => {
+ const { destinationOffset } = t.params;
+ const querySet = t.device.createQuerySet({ type: 'occlusion', count: kQueryCount });
+ const destination = t.device.createBuffer({
+ size: 512,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, 0, kQueryCount, destination, destinationOffset);
+ encoder.validateFinish(destinationOffset % 256 === 0);
+});
+
+g.test('resolve_buffer_oob').
+desc(
+ `
+Tests that resolve query set with the size oob:
+- The size of destinationBuffer - destinationOffset < queryCount * 8
+ `
+).
+paramsSubcasesOnly((u) =>
+u.combineWithParams([
+{ queryCount: 2, bufferSize: 16, destinationOffset: 0, _success: true },
+{ queryCount: 3, bufferSize: 16, destinationOffset: 0, _success: false },
+{ queryCount: 2, bufferSize: 16, destinationOffset: 256, _success: false },
+{ queryCount: 2, bufferSize: 272, destinationOffset: 256, _success: true },
+{ queryCount: 2, bufferSize: 264, destinationOffset: 256, _success: false }]
+)
+).
+fn((t) => {
+ const { queryCount, bufferSize, destinationOffset, _success } = t.params;
+ const querySet = t.device.createQuerySet({ type: 'occlusion', count: queryCount });
+ const destination = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, 0, queryCount, destination, destinationOffset);
+ encoder.validateFinish(_success);
+});
+
+g.test('query_set_buffer,device_mismatch').
+desc(
+ 'Tests resolveQuerySet cannot be called with a query set or destination buffer created from another device'
+).
+paramsSubcasesOnly([
+{ querySetMismatched: false, bufferMismatched: false }, // control case
+{ querySetMismatched: true, bufferMismatched: false },
+{ querySetMismatched: false, bufferMismatched: true }]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { querySetMismatched, bufferMismatched } = t.params;
+
+ const kQueryCount = 1;
+
+ const querySetDevice = querySetMismatched ? t.mismatchedDevice : t.device;
+ const querySet = querySetDevice.createQuerySet({
+ type: 'occlusion',
+ count: kQueryCount
+ });
+ t.trackForCleanup(querySet);
+
+ const bufferDevice = bufferMismatched ? t.mismatchedDevice : t.device;
+ const buffer = bufferDevice.createBuffer({
+ size: kQueryCount * 8,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ });
+ t.trackForCleanup(buffer);
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, 0, kQueryCount, buffer, 0);
+ encoder.validateFinish(!(querySetMismatched || bufferMismatched));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/render_bundle.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/render_bundle.spec.js
new file mode 100644
index 0000000000..49e9902f8a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/encoding/render_bundle.spec.js
@@ -0,0 +1,258 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests execution of render bundles.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('empty_bundle_list').
+desc(
+ `
+ Test that it is valid to execute an empty list of render bundles
+ `
+).
+fn((t) => {
+ const encoder = t.createEncoder('render pass');
+ encoder.encoder.executeBundles([]);
+ encoder.validateFinish(true);
+});
+
+g.test('device_mismatch').
+desc(
+ `
+ Tests executeBundles cannot be called with render bundles created from another device
+ Test with two bundles to make sure all bundles can be validated:
+ - bundle0 and bundle1 from same device
+ - bundle0 and bundle1 from different device
+ `
+).
+paramsSubcasesOnly([
+{ bundle0Mismatched: false, bundle1Mismatched: false }, // control case
+{ bundle0Mismatched: true, bundle1Mismatched: false },
+{ bundle0Mismatched: false, bundle1Mismatched: true }]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { bundle0Mismatched, bundle1Mismatched } = t.params;
+
+ const descriptor = {
+ colorFormats: ['rgba8unorm']
+ };
+
+ const bundle0Device = bundle0Mismatched ? t.mismatchedDevice : t.device;
+ const bundle0 = bundle0Device.createRenderBundleEncoder(descriptor).finish();
+
+ const bundle1Device = bundle1Mismatched ? t.mismatchedDevice : t.device;
+ const bundle1 = bundle1Device.createRenderBundleEncoder(descriptor).finish();
+
+ const encoder = t.createEncoder('render pass');
+ encoder.encoder.executeBundles([bundle0, bundle1]);
+
+ encoder.validateFinish(!(bundle0Mismatched || bundle1Mismatched));
+});
+
+g.test('color_formats_mismatch').
+desc(
+ `
+ Tests executeBundles cannot be called with render bundles that do match the colorFormats of the
+ render pass. This includes:
+ - formats don't match
+ - formats match but are in a different order
+ - formats match but there is a different count
+ `
+).
+params((u) =>
+u.combineWithParams([
+{
+ bundleFormats: ['bgra8unorm', 'rg8unorm'],
+ passFormats: ['bgra8unorm', 'rg8unorm'],
+ _compatible: true
+}, // control case
+{
+ bundleFormats: ['bgra8unorm', 'rg8unorm'],
+ passFormats: ['bgra8unorm', 'bgra8unorm'],
+ _compatible: false
+},
+{
+ bundleFormats: ['bgra8unorm', 'rg8unorm'],
+ passFormats: ['rg8unorm', 'bgra8unorm'],
+ _compatible: false
+},
+{
+ bundleFormats: ['bgra8unorm', 'rg8unorm', 'rgba8unorm'],
+ passFormats: ['rg8unorm', 'bgra8unorm'],
+ _compatible: false
+},
+{
+ bundleFormats: ['bgra8unorm', 'rg8unorm'],
+ passFormats: ['rg8unorm', 'bgra8unorm', 'rgba8unorm'],
+ _compatible: false
+}]
+)
+).
+fn((t) => {
+ const { bundleFormats, passFormats, _compatible } = t.params;
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: bundleFormats
+ });
+ const bundle = bundleEncoder.finish();
+
+ const encoder = t.createEncoder('render pass', {
+ attachmentInfo: {
+ colorFormats: passFormats
+ }
+ });
+ encoder.encoder.executeBundles([bundle]);
+
+ encoder.validateFinish(_compatible);
+});
+
+g.test('depth_stencil_formats_mismatch').
+desc(
+ `
+ Tests executeBundles cannot be called with render bundles that do match the depthStencil of the
+ render pass. This includes:
+ - formats don't match
+ - formats have matching depth or stencil aspects, but other aspects are missing
+ `
+).
+params((u) =>
+u.combineWithParams([
+{ bundleFormat: 'depth24plus', passFormat: 'depth24plus' }, // control case
+{ bundleFormat: 'depth24plus', passFormat: 'depth16unorm' },
+{ bundleFormat: 'depth24plus', passFormat: 'depth24plus-stencil8' },
+{ bundleFormat: 'stencil8', passFormat: 'depth24plus-stencil8' }]
+)
+).
+beforeAllSubcases((t) => {
+ const { bundleFormat, passFormat } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase([bundleFormat, passFormat]);
+}).
+fn((t) => {
+ const { bundleFormat, passFormat } = t.params;
+ const compatible = bundleFormat === passFormat;
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: [],
+ depthStencilFormat: bundleFormat
+ });
+ const bundle = bundleEncoder.finish();
+
+ const encoder = t.createEncoder('render pass', {
+ attachmentInfo: {
+ colorFormats: [],
+ depthStencilFormat: passFormat
+ }
+ });
+ encoder.encoder.executeBundles([bundle]);
+
+ encoder.validateFinish(compatible);
+});
+
+g.test('depth_stencil_readonly_mismatch').
+desc(
+ `
+ Tests executeBundles cannot be called with render bundles that do match the depthStencil
+ readonly state of the render pass.
+ `
+).
+params((u) =>
+u.
+combine('depthStencilFormat', kDepthStencilFormats).
+beginSubcases().
+combine('bundleDepthReadOnly', [false, true]).
+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);
+}).
+fn((t) => {
+ const {
+ depthStencilFormat,
+ bundleDepthReadOnly,
+ bundleStencilReadOnly,
+ passDepthReadOnly,
+ passStencilReadOnly
+ } = t.params;
+
+ const compatible =
+ (!passDepthReadOnly || bundleDepthReadOnly === passDepthReadOnly) && (
+ !passStencilReadOnly || bundleStencilReadOnly === passStencilReadOnly);
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: [],
+ depthStencilFormat,
+ depthReadOnly: bundleDepthReadOnly,
+ stencilReadOnly: bundleStencilReadOnly
+ });
+ const bundle = bundleEncoder.finish();
+
+ const encoder = t.createEncoder('render pass', {
+ attachmentInfo: {
+ colorFormats: [],
+ depthStencilFormat,
+ depthReadOnly: passDepthReadOnly,
+ stencilReadOnly: passStencilReadOnly
+ }
+ });
+ encoder.encoder.executeBundles([bundle]);
+
+ encoder.validateFinish(compatible);
+});
+
+g.test('sample_count_mismatch').
+desc(
+ `
+ Tests executeBundles cannot be called with render bundles that do match the sampleCount of the
+ render pass.
+ `
+).
+params((u) =>
+u.combineWithParams([
+{ bundleSamples: 1, passSamples: 1 }, // control case
+{ bundleSamples: 4, passSamples: 4 }, // control case
+{ bundleFormat: 4, passFormat: 1 },
+{ bundleFormat: 1, passFormat: 4 }]
+)
+).
+fn((t) => {
+ const { bundleSamples, passSamples } = t.params;
+
+ const compatible = bundleSamples === passSamples;
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['bgra8unorm'],
+ sampleCount: bundleSamples
+ });
+ const bundle = bundleEncoder.finish();
+
+ const encoder = t.createEncoder('render pass', {
+ attachmentInfo: {
+ colorFormats: ['bgra8unorm'],
+ sampleCount: passSamples
+ }
+ });
+ encoder.encoder.executeBundles([bundle]);
+
+ encoder.validateFinish(compatible);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/error_scope.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/error_scope.spec.js
new file mode 100644
index 0000000000..e5d76f175c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/error_scope.spec.js
@@ -0,0 +1,291 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Error scope validation tests.
+
+Note these must create their own device, not use GPUTest (that one already has error scopes on it).
+
+TODO: (POSTV1) Test error scopes of different threads and make sure they go to the right place.
+TODO: (POSTV1) Test that unhandled errors go the right device, and nowhere if the device was dropped.
+`;import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { getGPU } from '../../../common/util/navigator_gpu.js';
+import { assert, raceWithRejectOnTimeout } from '../../../common/util/util.js';
+import { kErrorScopeFilters, kGeneratableErrorScopeFilters } from '../../capability_info.js';
+
+class ErrorScopeTests extends Fixture {
+ _device = undefined;
+
+ get device() {
+ assert(this._device !== undefined);
+ return this._device;
+ }
+
+ async init() {
+ await super.init();
+ const gpu = getGPU(this.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+ const device = await adapter.requestDevice();
+ assert(device !== null);
+ this._device = device;
+ }
+
+ // Generates an error of the given filter type. For now, the errors are generated by calling a
+ // known code-path to cause the error. This can be updated in the future should there be a more
+ // direct way to inject errors.
+ generateError(filter) {
+ switch (filter) {
+ case 'out-of-memory':
+ this.trackForCleanup(
+ this.device.createTexture({
+ // One of the largest formats. With the base limits, the texture will be 256 GiB.
+ format: 'rgba32float',
+ usage: GPUTextureUsage.COPY_DST,
+ size: [
+ this.device.limits.maxTextureDimension2D,
+ this.device.limits.maxTextureDimension2D,
+ this.device.limits.maxTextureArrayLayers]
+
+ })
+ );
+ break;
+ case 'validation':
+ // Generating a validation error by passing in an invalid usage when creating a buffer.
+ this.trackForCleanup(
+ this.device.createBuffer({
+ size: 1024,
+ usage: 0xffff // Invalid GPUBufferUsage
+ })
+ );
+ break;
+ }
+ // MAINTENANCE_TODO: This is a workaround for Chromium not flushing. Remove when not needed.
+ this.device.queue.submit([]);
+ }
+
+ // Checks whether the error is of the type expected given the filter.
+ isInstanceOfError(filter, error) {
+ switch (filter) {
+ case 'out-of-memory':
+ return error instanceof GPUOutOfMemoryError;
+ case 'validation':
+ return error instanceof GPUValidationError;
+ case 'internal':
+ return error instanceof GPUInternalError;
+ }
+ }
+
+ // Expect an uncapturederror event to occur. Note: this MUST be awaited, because
+ // otherwise it could erroneously pass by capturing an error from later in the test.
+ async expectUncapturedError(fn) {
+ return this.immediateAsyncExpectation(() => {
+ // MAINTENANCE_TODO: Make arbitrary timeout value a test runner variable
+ const TIMEOUT_IN_MS = 1000;
+
+ const promise = new Promise((resolve) => {
+ const eventListener = (event) => {
+ this.debug(`Got uncaptured error event with ${event.error}`);
+ resolve(event);
+ };
+
+ this.device.addEventListener('uncapturederror', eventListener, { once: true });
+ });
+
+ fn();
+
+ return raceWithRejectOnTimeout(
+ promise,
+ TIMEOUT_IN_MS,
+ 'Timeout occurred waiting for uncaptured error'
+ );
+ });
+ }
+}
+
+export const g = makeTestGroup(ErrorScopeTests);
+
+g.test('simple').
+desc(
+ `
+Tests that error scopes catches their expected errors, firing an uncaptured error event otherwise.
+
+- Same error and error filter (popErrorScope should return the error)
+- Different error from filter (uncaptured error should result)
+ `
+).
+params((u) =>
+u.combine('errorType', kGeneratableErrorScopeFilters).combine('errorFilter', kErrorScopeFilters)
+).
+fn(async (t) => {
+ const { errorType, errorFilter } = t.params;
+ t.device.pushErrorScope(errorFilter);
+
+ if (errorType !== errorFilter) {
+ // Different error case
+ const uncapturedErrorEvent = await t.expectUncapturedError(() => {
+ t.generateError(errorType);
+ });
+ t.expect(t.isInstanceOfError(errorType, uncapturedErrorEvent.error));
+
+ const error = await t.device.popErrorScope();
+ t.expect(error === null);
+ } else {
+ // Same error as filter
+ t.generateError(errorType);
+ const error = await t.device.popErrorScope();
+ t.expect(t.isInstanceOfError(errorType, error));
+ }
+});
+
+g.test('empty').
+desc(
+ `
+Tests that popping an empty error scope stack should reject.
+ `
+).
+fn((t) => {
+ const promise = t.device.popErrorScope();
+ t.shouldReject('OperationError', promise);
+});
+
+g.test('parent_scope').
+desc(
+ `
+Tests that an error bubbles to the correct parent scope.
+
+- Different error types as the parent scope
+- Different depths of non-capturing filters for the generated error
+ `
+).
+params((u) =>
+u.
+combine('errorFilter', kGeneratableErrorScopeFilters).
+combine('stackDepth', [1, 10, 100, 1000])
+).
+fn(async (t) => {
+ const { errorFilter, stackDepth } = t.params;
+ t.device.pushErrorScope(errorFilter);
+
+ // Push a bunch of error filters onto the stack (none that match errorFilter)
+ const unmatchedFilters = kErrorScopeFilters.filter((filter) => {
+ return filter !== errorFilter;
+ });
+ for (let i = 0; i < stackDepth; i++) {
+ t.device.pushErrorScope(unmatchedFilters[i % unmatchedFilters.length]);
+ }
+
+ // Cause the error and then pop all the unrelated filters.
+ t.generateError(errorFilter);
+ const promises = [];
+ for (let i = 0; i < stackDepth; i++) {
+ promises.push(t.device.popErrorScope());
+ }
+ const errors = await Promise.all(promises);
+ t.expect(errors.every((e) => e === null));
+
+ // Finally the actual error should have been caught by the parent scope.
+ const error = await t.device.popErrorScope();
+ t.expect(t.isInstanceOfError(errorFilter, error));
+});
+
+g.test('current_scope').
+desc(
+ `
+Tests that an error does not bubbles to parent scopes when local scope matches.
+
+- Different error types as the current scope
+- Different depths of non-capturing filters for the generated error
+ `
+).
+params((u) =>
+u.
+combine('errorFilter', kGeneratableErrorScopeFilters).
+combine('stackDepth', [1, 10, 100, 1000, 100000])
+).
+fn(async (t) => {
+ const { errorFilter, stackDepth } = t.params;
+
+ // Push a bunch of error filters onto the stack
+ for (let i = 0; i < stackDepth; i++) {
+ t.device.pushErrorScope(kErrorScopeFilters[i % kErrorScopeFilters.length]);
+ }
+
+ // Current scope should catch the error immediately.
+ t.device.pushErrorScope(errorFilter);
+ t.generateError(errorFilter);
+ const error = await t.device.popErrorScope();
+ t.expect(t.isInstanceOfError(errorFilter, error));
+
+ // Remaining scopes shouldn't catch anything.
+ const promises = [];
+ for (let i = 0; i < stackDepth; i++) {
+ promises.push(t.device.popErrorScope());
+ }
+ const errors = await Promise.all(promises);
+ t.expect(errors.every((e) => e === null));
+});
+
+g.test('balanced_siblings').
+desc(
+ `
+Tests that sibling error scopes need to be balanced.
+
+- Different error types as the current scope
+- Different number of sibling errors
+ `
+).
+params((u) =>
+u.combine('errorFilter', kErrorScopeFilters).combine('numErrors', [1, 10, 100, 1000])
+).
+fn(async (t) => {
+ const { errorFilter, numErrors } = t.params;
+
+ const promises = [];
+ for (let i = 0; i < numErrors; i++) {
+ t.device.pushErrorScope(errorFilter);
+ promises.push(t.device.popErrorScope());
+ }
+
+ {
+ // Trying to pop an additional non-existing scope should reject.
+ const promise = t.device.popErrorScope();
+ t.shouldReject('OperationError', promise);
+ }
+
+ const errors = await Promise.all(promises);
+ t.expect(errors.every((e) => e === null));
+});
+
+g.test('balanced_nesting').
+desc(
+ `
+Tests that nested error scopes need to be balanced.
+
+- Different error types as the current scope
+- Different number of nested errors
+ `
+).
+params((u) =>
+u.combine('errorFilter', kErrorScopeFilters).combine('numErrors', [1, 10, 100, 1000])
+).
+fn(async (t) => {
+ const { errorFilter, numErrors } = t.params;
+
+ for (let i = 0; i < numErrors; i++) {
+ t.device.pushErrorScope(errorFilter);
+ }
+
+ const promises = [];
+ for (let i = 0; i < numErrors; i++) {
+ promises.push(t.device.popErrorScope());
+ }
+ const errors = await Promise.all(promises);
+ t.expect(errors.every((e) => e === null));
+
+ {
+ // Trying to pop an additional non-existing scope should reject.
+ const promise = t.device.popErrorScope();
+ t.shouldReject('OperationError', promise);
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/getBindGroupLayout.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/getBindGroupLayout.spec.js
new file mode 100644
index 0000000000..f6a4addbe3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/getBindGroupLayout.spec.js
@@ -0,0 +1,201 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+ getBindGroupLayout validation tests.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert } from '../../../common/util/util.js';
+
+import { ValidationTest } from './validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('index_range,explicit_layout').
+desc(
+ `
+ Test that a validation error is generated if the index exceeds the size of the bind group layouts
+ using a pipeline with an explicit layout.
+ `
+).
+params((u) => u.combine('index', [0, 1, 2, 3, 4, 5])).
+fn((t) => {
+ const { index } = t.params;
+
+ const pipelineBindGroupLayouts = t.device.createBindGroupLayout({
+ entries: []
+ });
+
+ const kBindGroupLayoutsSizeInPipelineLayout = 1;
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [pipelineBindGroupLayouts]
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: pipelineLayout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main()-> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+
+ const shouldError = index >= kBindGroupLayoutsSizeInPipelineLayout;
+
+ t.expectValidationError(() => {
+ pipeline.getBindGroupLayout(index);
+ }, shouldError);
+});
+
+g.test('index_range,auto_layout').
+desc(
+ `
+ Test that a validation error is generated if the index exceeds the size of the bind group layouts
+ using a pipeline with an auto layout.
+ `
+).
+params((u) => u.combine('index', [0, 1, 2, 3, 4, 5])).
+fn((t) => {
+ const { index } = t.params;
+
+ const kBindGroupLayoutsSizeInPipelineLayout = 1;
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main()-> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var<uniform> binding: f32;
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ _ = binding;
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+
+ const shouldError = index >= kBindGroupLayoutsSizeInPipelineLayout;
+
+ t.expectValidationError(() => {
+ pipeline.getBindGroupLayout(index);
+ }, shouldError);
+});
+
+g.test('unique_js_object,auto_layout').
+desc(
+ `
+ Test that getBindGroupLayout returns a new JavaScript object for each call.
+ `
+).
+fn((t) => {
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main()-> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var<uniform> binding: f32;
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ _ = binding;
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+
+ const kIndex = 0;
+ const bgl1 = pipeline.getBindGroupLayout(kIndex);
+ bgl1.extra = 42;
+ const bgl2 = pipeline.getBindGroupLayout(kIndex);
+
+ assert(bgl1 !== bgl2, 'objects are not the same object');
+ assert(bgl2.extra === undefined, 'objects do not retain expando properties');
+});
+
+g.test('unique_js_object,explicit_layout').
+desc(
+ `
+ Test that getBindGroupLayout returns a new JavaScript object for each call.
+ `
+).
+fn((t) => {
+ const pipelineBindGroupLayouts = t.device.createBindGroupLayout({
+ entries: []
+ });
+
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [pipelineBindGroupLayouts]
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: pipelineLayout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main()-> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+
+ const kIndex = 0;
+ const bgl1 = pipeline.getBindGroupLayout(kIndex);
+ bgl1.extra = 42;
+ const bgl2 = pipeline.getBindGroupLayout(kIndex);
+
+ assert(bgl1 !== bgl2, 'objects are not the same object');
+ assert(bgl2.extra === undefined, 'objects do not retain expando properties');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/gpu_external_texture_expiration.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/gpu_external_texture_expiration.spec.js
new file mode 100644
index 0000000000..4afd966260
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/gpu_external_texture_expiration.spec.js
@@ -0,0 +1,332 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+GPUExternalTexture expiration mechanism validation tests.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert } from '../../../common/util/util.js';
+import {
+ getVideoElement,
+ startPlayingAndWaitForVideo,
+ getVideoFrameFromVideoElement,
+ waitForNextFrame,
+ waitForNextTask } from
+'../../web_platform/util.js';
+
+import { ValidationTest } from './validation_test.js';
+
+class GPUExternalTextureExpireTest extends ValidationTest {
+ submitCommandBuffer(bindGroup, success) {
+ const kHeight = 16;
+ const kWidth = 16;
+ const kFormat = 'rgba8unorm';
+
+ const colorAttachment = this.device.createTexture({
+ format: kFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const passDescriptor = {
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: [0, 0, 0, 1],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ const commandEncoder = this.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(passDescriptor);
+ passEncoder.setBindGroup(0, bindGroup);
+ passEncoder.end();
+ const commandBuffer = commandEncoder.finish();
+ this.expectValidationError(() => this.device.queue.submit([commandBuffer]), !success);
+ }
+
+ getDefaultVideoElementAndCheck() {
+ const videoElement = getVideoElement(this, 'four-colors-vp9-bt601.webm');
+
+ if (!('requestVideoFrameCallback' in videoElement)) {
+ this.skip('HTMLVideoElement.requestVideoFrameCallback is not supported');
+ }
+
+ return videoElement;
+ }
+
+ getDefaultBindGroupLayout() {
+ return this.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, externalTexture: {} }]
+ });
+ }
+}
+
+export const g = makeTestGroup(GPUExternalTextureExpireTest);
+
+g.test('import_multiple_times_in_same_task_scope').
+desc(
+ `
+ Tests that GPUExternalTexture is valid after been imported in the task.
+ Tests that in the same task scope, import twice on the same video source may return
+ the same GPUExternalTexture and bindGroup doesn't need to be updated.
+ `
+).
+params((u) =>
+u //
+.combine('sourceType', ['VideoElement', 'VideoFrame'])
+).
+fn(async (t) => {
+ const sourceType = t.params.sourceType;
+ const videoElement = t.getDefaultVideoElementAndCheck();
+
+ let bindGroup;
+ let externalTexture;
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+ externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+
+ t.submitCommandBuffer(bindGroup, true);
+
+ // Import again in the same task scope should return same object.
+ const mayBeTheSameExternalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ if (externalTexture === mayBeTheSameExternalTexture) {
+ t.submitCommandBuffer(bindGroup, true);
+ } else {
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+
+ t.submitCommandBuffer(bindGroup, true);
+ }
+ });
+});
+
+g.test('import_and_use_in_different_microtask').
+desc(
+ `
+ Tests that in the same task scope, imported GPUExternalTexture is valid in
+ different microtasks.
+ `
+).
+params((u) =>
+u //
+.combine('sourceType', ['VideoElement', 'VideoFrame'])
+).
+fn(async (t) => {
+ const sourceType = t.params.sourceType;
+ const videoElement = t.getDefaultVideoElementAndCheck();
+
+ let bindGroup;
+ let externalTexture;
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+
+ // Import GPUExternalTexture
+ queueMicrotask(() => {
+ externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+ });
+
+ // Submit GPUExternalTexture
+ queueMicrotask(() => {
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+ t.submitCommandBuffer(bindGroup, true);
+ });
+ });
+});
+
+g.test('import_and_use_in_different_task').
+desc(
+ `
+ Tests that in the different task scope, previous imported GPUExternalTexture
+ should be expired if it is imported from HTMLVideoElment. GPUExternalTexture
+ imported from WebCodec VideoFrame is not expired.
+ `
+).
+params((u) =>
+u //
+.combine('sourceType', ['VideoElement', 'VideoFrame'])
+).
+fn(async (t) => {
+ const sourceType = t.params.sourceType;
+ const videoElement = t.getDefaultVideoElementAndCheck();
+
+ let bindGroup;
+ let externalTexture;
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+ externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+
+ t.submitCommandBuffer(bindGroup, true);
+ });
+
+ await waitForNextTask(() => {
+ // Enter in another task scope. For GPUExternalTexture imported from WebCodec,
+ // it shouldn't be expired because VideoFrame is not 'closed'.
+ // For GPUExternalTexutre imported from HTMLVideoElement, it should be expired.
+ t.submitCommandBuffer(bindGroup, sourceType === 'VideoFrame' ? true : false);
+ });
+});
+
+g.test('use_import_to_refresh').
+desc(
+ `
+ Tests that in the different task scope, imported GPUExternalTexture
+ again on the same HTMLVideoElement should return active GPUExternalTexture.
+ `
+).
+fn(async (t) => {
+ const videoElement = t.getDefaultVideoElementAndCheck();
+
+ let bindGroup;
+ let externalTexture;
+ let source;
+ await startPlayingAndWaitForVideo(videoElement, () => {
+ source = videoElement;
+ externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+
+ t.submitCommandBuffer(bindGroup, true);
+ });
+
+ await waitForNextTask(() => {
+ const mayBeTheSameExternalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ if (externalTexture === mayBeTheSameExternalTexture) {
+ // ImportExternalTexture should refresh expired GPUExternalTexture.
+ t.submitCommandBuffer(bindGroup, true);
+ } else {
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+ t.submitCommandBuffer(bindGroup, true);
+ }
+ });
+});
+
+g.test('webcodec_video_frame_close_expire_immediately').
+desc(
+ `
+ Tests that in the same task scope, imported GPUExternalTexture should be expired
+ immediately if webcodec VideoFrame.close() is called.
+ `
+).
+fn(async (t) => {
+ const videoElement = t.getDefaultVideoElementAndCheck();
+
+ let bindGroup;
+ let externalTexture;
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source = await getVideoFrameFromVideoElement(t, videoElement);
+ externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+
+ t.submitCommandBuffer(bindGroup, true);
+
+ source.close();
+
+ t.submitCommandBuffer(bindGroup, false);
+ });
+});
+
+g.test('import_from_different_video_frame').
+desc(
+ `
+ Tests that imported GPUExternalTexture from different video frame should
+ return different GPUExternalTexture objects.
+ If the frames are from the same HTMLVideoElement source, GPUExternalTexture
+ with old frame should be expired and not been refreshed again.
+ `
+).
+fn(async (t) => {
+ const videoElement = t.getDefaultVideoElementAndCheck();
+
+ let bindGroup;
+ let externalTexture;
+ await startPlayingAndWaitForVideo(videoElement, () => {
+ externalTexture = t.device.importExternalTexture({
+ source: videoElement
+ });
+
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: externalTexture }]
+ });
+
+ t.submitCommandBuffer(bindGroup, true);
+ });
+
+ // Update new video frame.
+ await waitForNextFrame(videoElement, () => {
+ // Import again for the new video frame.
+ const newValidExternalTexture = t.device.importExternalTexture({
+ source: videoElement
+ });
+ assert(externalTexture !== newValidExternalTexture);
+
+ // VideoFrame is updated. GPUExternalTexture imported from old frame should be expired and
+ // cannot be refreshed again.
+ // Using the GPUExternalTexture should result in an error.
+ t.submitCommandBuffer(bindGroup, false);
+
+ // Update bindGroup with updated GPUExternalTexture should work.
+ bindGroup = t.device.createBindGroup({
+ layout: t.getDefaultBindGroupLayout(),
+ entries: [{ binding: 0, resource: newValidExternalTexture }]
+ });
+ t.submitCommandBuffer(bindGroup, true);
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_related.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_related.spec.js
new file mode 100644
index 0000000000..d22e39eecb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_related.spec.js
@@ -0,0 +1,226 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for buffer related parameters for buffer <-> texture copies`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureDimensions } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import {
+ kSizedTextureFormats,
+ kTextureFormatInfo,
+ textureDimensionAndFormatCompatible } from
+'../../../format_info.js';
+import { kResourceStates } from '../../../gpu_test.js';
+import { kImageCopyTypes } from '../../../util/texture/layout.js';
+
+import { ImageCopyTest, formatCopyableWithMethod } from './image_copy.js';
+
+export const g = makeTestGroup(ImageCopyTest);
+
+g.test('buffer_state').
+desc(
+ `
+Test that the buffer must be valid and not destroyed.
+- for all buffer <-> texture copy methods
+- for various buffer states
+`
+).
+params((u) =>
+u //
+// B2B copy validations are at api,validation,encoding,cmds,copyBufferToBuffer.spec.ts
+.combine('method', ['CopyB2T', 'CopyT2B']).
+combine('state', kResourceStates)
+).
+fn((t) => {
+ const { method, state } = t.params;
+
+ // A valid buffer.
+ const buffer = t.createBufferWithState(state, {
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ // Invalid buffer will fail finish, and destroyed buffer will fail submit
+ const submit = state !== 'invalid';
+ const success = state === 'valid';
+
+ const texture = t.device.createTexture({
+ size: { width: 2, height: 2, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ t.testBuffer(
+ buffer,
+ texture,
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 16, method, success, submit }
+ );
+});
+
+g.test('buffer,device_mismatch').
+desc('Tests the image copies cannot be called with a buffer created from another device').
+paramsSubcasesOnly((u) =>
+u.combine('method', ['CopyB2T', 'CopyT2B']).combine('mismatched', [true, false])
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { method, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const buffer = sourceDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(buffer);
+
+ const texture = t.device.createTexture({
+ size: { width: 2, height: 2, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const success = !mismatched;
+
+ // Expect success in both finish and submit, or validation error in finish
+ t.testBuffer(
+ buffer,
+ texture,
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 16, method, success, submit: success }
+ );
+});
+
+g.test('usage').
+desc(
+ `
+Test the buffer must have the appropriate COPY_SRC/COPY_DST usage.
+TODO update such that it tests
+- for all buffer source usages
+- for all buffer destination usages
+`
+).
+params((u) =>
+u
+// B2B copy validations are at api,validation,encoding,cmds,copyBufferToBuffer.spec.ts
+.combine('method', ['CopyB2T', 'CopyT2B']).
+beginSubcases().
+combine('usage', [
+GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.UNIFORM,
+GPUConst.BufferUsage.COPY_DST | GPUConst.BufferUsage.UNIFORM,
+GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.COPY_DST]
+)
+).
+fn((t) => {
+ const { method, usage } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 16,
+ usage
+ });
+
+ const success =
+ method === 'CopyB2T' ?
+ (usage & GPUBufferUsage.COPY_SRC) !== 0 :
+ (usage & GPUBufferUsage.COPY_DST) !== 0;
+
+ const texture = t.device.createTexture({
+ size: { width: 2, height: 2, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ // Expect success in both finish and submit, or validation error in finish
+ t.testBuffer(
+ buffer,
+ texture,
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 16, method, success, submit: success }
+ );
+});
+
+g.test('bytes_per_row_alignment').
+desc(
+ `
+Test that bytesPerRow must be a multiple of 256 for CopyB2T and CopyT2B if it is required.
+- for all copy methods between linear data and textures
+- for all texture dimensions
+- for all sized formats.
+- for various bytesPerRow aligned to 256 or not
+- for various number of blocks rows copied
+`
+).
+params((u) =>
+u //
+.combine('method', kImageCopyTypes).
+combine('format', kSizedTextureFormats).
+filter(formatCopyableWithMethod).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combine('bytesPerRow', [undefined, 0, 1, 255, 256, 257, 512]).
+combine('copyHeightInBlocks', [0, 1, 2, 3]).
+expand('_textureHeightInBlocks', (p) => [
+p.copyHeightInBlocks === 0 ? 1 : p.copyHeightInBlocks]
+).
+unless((p) => p.dimension === '1d' && p.copyHeightInBlocks > 1)
+// Depth/stencil format copies must copy the whole subresource.
+.unless((p) => {
+ const info = kTextureFormatInfo[p.format];
+ return (
+ (!!info.depth || !!info.stencil) && p.copyHeightInBlocks !== p._textureHeightInBlocks);
+
+})
+// bytesPerRow must be specified and it must be equal or greater than the bytes size of each row if we are copying multiple rows.
+// Note that we are copying one single block on each row in this test.
+.filter(
+ ({ format, bytesPerRow, copyHeightInBlocks }) =>
+ bytesPerRow === undefined && copyHeightInBlocks <= 1 ||
+ bytesPerRow !== undefined && bytesPerRow >= kTextureFormatInfo[format].bytesPerBlock
+)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { method, dimension, format, bytesPerRow, copyHeightInBlocks, _textureHeightInBlocks } =
+ t.params;
+
+ const info = kTextureFormatInfo[format];
+
+ const buffer = t.device.createBuffer({
+ size: 512 * 8 * 16,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ let success = false;
+ // writeTexture doesn't require bytesPerRow to be 256-byte aligned.
+ if (method === 'WriteTexture') success = true;
+ // If the copy height <= 1, bytesPerRow is not required.
+ if (copyHeightInBlocks <= 1 && bytesPerRow === undefined) success = true;
+ // If bytesPerRow > 0 and it is a multiple of 256, it will succeed if other parameters are valid.
+ if (bytesPerRow !== undefined && bytesPerRow > 0 && bytesPerRow % 256 === 0) success = true;
+
+ const size = [info.blockWidth, _textureHeightInBlocks * info.blockHeight, 1];
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const copySize = [info.blockWidth, copyHeightInBlocks * info.blockHeight, 1];
+
+ // Expect success in both finish and submit, or validation error in finish
+ t.testBuffer(buffer, texture, { bytesPerRow }, copySize, {
+ dataSize: 512 * 8 * 16,
+ method,
+ success,
+ submit: success
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_texture_copies.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_texture_copies.spec.js
new file mode 100644
index 0000000000..9ea44a36d4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/buffer_texture_copies.spec.js
@@ -0,0 +1,453 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyTextureToBuffer and copyBufferToTexture validation tests not covered by
+the general image_copy tests, or by destroyed,*.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, unreachable } from '../../../../common/util/util.js';
+import { kBufferUsages, kTextureUsages } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import {
+ kDepthStencilFormats,
+ depthStencilBufferTextureCopySupported,
+ depthStencilFormatAspectSize } from
+'../../../format_info.js';
+import { align } from '../../../util/math.js';
+import { kBufferCopyAlignment, kBytesPerRowAlignment } from '../../../util/texture/layout.js';
+import { ValidationTest } from '../validation_test.js';
+
+class ImageCopyTest extends ValidationTest {
+ testCopyBufferToTexture(
+ source,
+ destination,
+ copySize,
+ isSuccess)
+ {
+ const { encoder, validateFinishAndSubmit } = this.createEncoder('non-pass');
+ encoder.copyBufferToTexture(source, destination, copySize);
+ validateFinishAndSubmit(isSuccess, true);
+ }
+
+ testCopyTextureToBuffer(
+ source,
+ destination,
+ copySize,
+ isSuccess)
+ {
+ const { encoder, validateFinishAndSubmit } = this.createEncoder('non-pass');
+ encoder.copyTextureToBuffer(source, destination, copySize);
+ validateFinishAndSubmit(isSuccess, true);
+ }
+
+ testWriteTexture(
+ destination,
+ uploadData,
+ dataLayout,
+ copySize,
+ isSuccess)
+ {
+ this.expectGPUError(
+ 'validation',
+ () => this.queue.writeTexture(destination, uploadData, dataLayout, copySize),
+ !isSuccess
+ );
+ }
+}
+
+export const g = makeTestGroup(ImageCopyTest);
+
+g.test('depth_stencil_format,copy_usage_and_aspect').
+desc(
+ `
+ Validate the combination of usage and aspect of each depth stencil format in copyBufferToTexture,
+ copyTextureToBuffer and writeTexture. See https://gpuweb.github.io/gpuweb/#depth-formats for more
+ details.
+ `
+).
+params((u) =>
+u //
+.combine('format', kDepthStencilFormats).
+beginSubcases().
+combine('aspect', ['all', 'depth-only', 'stencil-only'])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(format);
+}).
+fn((t) => {
+ const { format, aspect } = t.params;
+
+ const textureSize = { width: 1, height: 1, depthOrArrayLayers: 1 };
+ const texture = t.device.createTexture({
+ size: textureSize,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const uploadBufferSize = 32;
+ const buffer = t.device.createBuffer({
+ size: uploadBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ {
+ const success = depthStencilBufferTextureCopySupported('CopyB2T', format, aspect);
+ t.testCopyBufferToTexture({ buffer }, { texture, aspect }, textureSize, success);
+ }
+
+ {
+ const success = depthStencilBufferTextureCopySupported('CopyT2B', format, aspect);
+ t.testCopyTextureToBuffer({ texture, aspect }, { buffer }, textureSize, success);
+ }
+
+ {
+ const success = depthStencilBufferTextureCopySupported('WriteTexture', format, aspect);
+ const uploadData = new Uint8Array(uploadBufferSize);
+ t.testWriteTexture({ texture, aspect }, uploadData, {}, textureSize, success);
+ }
+});
+
+g.test('depth_stencil_format,copy_buffer_size').
+desc(
+ `
+ Validate the minimum buffer size for each depth stencil format in copyBufferToTexture,
+ copyTextureToBuffer and writeTexture.
+
+ Given a depth stencil format, a copy aspect ('depth-only' or 'stencil-only'), the copy method
+ (buffer-to-texture or texture-to-buffer) and the copy size, validate
+ - if the copy can be successfully executed with the minimum required buffer size.
+ - if the copy fails with a validation error when the buffer size is less than the minimum
+ required buffer size.
+ `
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+combine('aspect', ['depth-only', 'stencil-only']).
+combine('copyType', ['CopyB2T', 'CopyT2B', 'WriteTexture']).
+filter((param) =>
+depthStencilBufferTextureCopySupported(param.copyType, param.format, param.aspect)
+).
+beginSubcases().
+combine('copySize', [
+{ width: 8, height: 1, depthOrArrayLayers: 1 },
+{ width: 4, height: 4, depthOrArrayLayers: 1 },
+{ width: 4, height: 4, depthOrArrayLayers: 3 }]
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(format);
+}).
+fn((t) => {
+ const { format, aspect, copyType, copySize } = t.params;
+
+ const texture = t.device.createTexture({
+ size: copySize,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const texelAspectSize = depthStencilFormatAspectSize(format, aspect);
+ assert(texelAspectSize > 0);
+
+ const bytesPerRowAlignment = copyType === 'WriteTexture' ? 1 : kBytesPerRowAlignment;
+ const bytesPerRow = align(texelAspectSize * copySize.width, bytesPerRowAlignment);
+ const rowsPerImage = copySize.height;
+ const minimumBufferSize =
+ bytesPerRow * (rowsPerImage * copySize.depthOrArrayLayers - 1) +
+ align(texelAspectSize * copySize.width, kBufferCopyAlignment);
+ assert(minimumBufferSize > kBufferCopyAlignment);
+
+ const bigEnoughBuffer = t.device.createBuffer({
+ size: minimumBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ const smallerBuffer = t.device.createBuffer({
+ size: minimumBufferSize - kBufferCopyAlignment,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ if (copyType === 'CopyB2T') {
+ t.testCopyBufferToTexture(
+ { buffer: bigEnoughBuffer, bytesPerRow, rowsPerImage },
+ { texture, aspect },
+ copySize,
+ true
+ );
+ t.testCopyBufferToTexture(
+ { buffer: smallerBuffer, bytesPerRow, rowsPerImage },
+ { texture, aspect },
+ copySize,
+ false
+ );
+ } else if (copyType === 'CopyT2B') {
+ t.testCopyTextureToBuffer(
+ { texture, aspect },
+ { buffer: bigEnoughBuffer, bytesPerRow, rowsPerImage },
+ copySize,
+ true
+ );
+ t.testCopyTextureToBuffer(
+ { texture, aspect },
+ { buffer: smallerBuffer, bytesPerRow, rowsPerImage },
+ copySize,
+ false
+ );
+ } else if (copyType === 'WriteTexture') {
+ const enoughUploadData = new Uint8Array(minimumBufferSize);
+ const smallerUploadData = new Uint8Array(minimumBufferSize - kBufferCopyAlignment);
+ t.testWriteTexture(
+ { texture, aspect },
+ enoughUploadData,
+ {
+ bytesPerRow,
+ rowsPerImage
+ },
+ copySize,
+ true
+ );
+
+ t.testWriteTexture(
+ { texture, aspect },
+ smallerUploadData,
+ {
+ bytesPerRow,
+ rowsPerImage
+ },
+ copySize,
+ false
+ );
+ } else {
+ unreachable();
+ }
+});
+
+g.test('depth_stencil_format,copy_buffer_offset').
+desc(
+ `
+ Validate for every depth stencil formats the buffer offset must be a multiple of 4 in
+ copyBufferToTexture() and copyTextureToBuffer(), but the offset in writeTexture() doesn't always
+ need to be a multiple of 4.
+ `
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+combine('aspect', ['depth-only', 'stencil-only']).
+combine('copyType', ['CopyB2T', 'CopyT2B', 'WriteTexture']).
+filter((param) =>
+depthStencilBufferTextureCopySupported(param.copyType, param.format, param.aspect)
+).
+beginSubcases().
+combine('offset', [1, 2, 4, 6, 8])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceForTextureFormatOrSkipTestCase(format);
+}).
+fn((t) => {
+ const { format, aspect, copyType, offset } = t.params;
+
+ const textureSize = { width: 4, height: 4, depthOrArrayLayers: 1 };
+
+ const texture = t.device.createTexture({
+ size: textureSize,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const texelAspectSize = depthStencilFormatAspectSize(format, aspect);
+ assert(texelAspectSize > 0);
+
+ const bytesPerRowAlignment = copyType === 'WriteTexture' ? 1 : kBytesPerRowAlignment;
+ const bytesPerRow = align(texelAspectSize * textureSize.width, bytesPerRowAlignment);
+ const rowsPerImage = textureSize.height;
+ const minimumBufferSize =
+ bytesPerRow * (rowsPerImage * textureSize.depthOrArrayLayers - 1) +
+ align(texelAspectSize * textureSize.width, kBufferCopyAlignment);
+ assert(minimumBufferSize > kBufferCopyAlignment);
+
+ const buffer = t.device.createBuffer({
+ size: align(minimumBufferSize + offset, kBufferCopyAlignment),
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const isSuccess = copyType === 'WriteTexture' ? true : offset % 4 === 0;
+
+ if (copyType === 'CopyB2T') {
+ t.testCopyBufferToTexture(
+ { buffer, offset, bytesPerRow, rowsPerImage },
+ { texture, aspect },
+ textureSize,
+ isSuccess
+ );
+ } else if (copyType === 'CopyT2B') {
+ t.testCopyTextureToBuffer(
+ { texture, aspect },
+ { buffer, offset, bytesPerRow, rowsPerImage },
+ textureSize,
+ isSuccess
+ );
+ } else if (copyType === 'WriteTexture') {
+ const uploadData = new Uint8Array(minimumBufferSize + offset);
+ t.testWriteTexture(
+ { texture, aspect },
+ uploadData,
+ {
+ offset,
+ bytesPerRow,
+ rowsPerImage
+ },
+ textureSize,
+ isSuccess
+ );
+ } else {
+ unreachable();
+ }
+});
+
+g.test('sample_count').
+desc(
+ `
+ Test that the texture sample count. Check that a validation error is generated if sample count is
+ not 1.
+ `
+).
+params((u) =>
+u //
+// writeTexture is handled by writeTexture.spec.ts.
+.combine('copyType', ['CopyB2T', 'CopyT2B']).
+beginSubcases().
+combine('sampleCount', [1, 4])
+).
+fn((t) => {
+ const { sampleCount, copyType } = t.params;
+
+ let usage = GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST;
+ // WebGPU SPEC requires multisampled textures must have RENDER_ATTACHMENT usage.
+ if (sampleCount > 1) {
+ usage |= GPUTextureUsage.RENDER_ATTACHMENT;
+ }
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16 },
+ sampleCount,
+ format: 'bgra8unorm',
+ usage
+ });
+
+ const uploadBufferSize = 32;
+ const buffer = t.device.createBuffer({
+ size: uploadBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+
+ const textureSize = { width: 1, height: 1, depthOrArrayLayers: 1 };
+
+ const isSuccess = sampleCount === 1;
+
+ if (copyType === 'CopyB2T') {
+ t.testCopyBufferToTexture({ buffer }, { texture }, textureSize, isSuccess);
+ } else if (copyType === 'CopyT2B') {
+ t.testCopyTextureToBuffer({ texture }, { buffer }, textureSize, isSuccess);
+ }
+});
+
+const kRequiredTextureUsage = {
+ CopyT2B: GPUConst.TextureUsage.COPY_SRC,
+ CopyB2T: GPUConst.TextureUsage.COPY_DST
+};
+const kRequiredBufferUsage = {
+ CopyB2T: GPUConst.BufferUsage.COPY_SRC,
+ CopyT2B: GPUConst.BufferUsage.COPY_DST
+};
+
+g.test('texture_buffer_usages').
+desc(
+ `
+ Tests calling copyTextureToBuffer or copyBufferToTexture with the texture and the buffer missed
+ COPY_SRC, COPY_DST usage respectively.
+ - texture and buffer {with, without} COPY_SRC and COPY_DST usage.
+ `
+).
+params((u) =>
+u //
+.combine('copyType', ['CopyB2T', 'CopyT2B']).
+beginSubcases().
+combine('textureUsage', kTextureUsages).
+expand('_textureUsageValid', (p) => [p.textureUsage === kRequiredTextureUsage[p.copyType]]).
+combine('bufferUsage', kBufferUsages).
+expand('_bufferUsageValid', (p) => [p.bufferUsage === kRequiredBufferUsage[p.copyType]]).
+filter((p) => p._textureUsageValid || p._bufferUsageValid)
+).
+fn((t) => {
+ const { copyType, textureUsage, _textureUsageValid, bufferUsage, _bufferUsageValid } = t.params;
+
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16 },
+ format: 'rgba8unorm',
+ usage: textureUsage
+ });
+
+ const uploadBufferSize = 32;
+ const buffer = t.device.createBuffer({
+ size: uploadBufferSize,
+ usage: bufferUsage
+ });
+
+ const textureSize = { width: 1, height: 1, depthOrArrayLayers: 1 };
+
+ const isSuccess = _textureUsageValid && _bufferUsageValid;
+ if (copyType === 'CopyB2T') {
+ t.testCopyBufferToTexture({ buffer }, { texture }, textureSize, isSuccess);
+ } else if (copyType === 'CopyT2B') {
+ t.testCopyTextureToBuffer({ texture }, { buffer }, textureSize, isSuccess);
+ }
+});
+
+g.test('device_mismatch').
+desc(
+ `
+ Tests copyBufferToTexture and copyTextureToBuffer cannot be called with a buffer or a texture
+ created from another device.
+ `
+).
+params((u) =>
+u //
+.combine('copyType', ['CopyB2T', 'CopyT2B']).
+beginSubcases().
+combineWithParams([
+{ bufMismatched: false, texMismatched: false }, // control case
+{ bufMismatched: true, texMismatched: false },
+{ bufMismatched: false, texMismatched: true }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { copyType, bufMismatched, texMismatched } = t.params;
+
+ const uploadBufferSize = 32;
+ const buffer = (bufMismatched ? t.mismatchedDevice : t.device).createBuffer({
+ size: uploadBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(buffer);
+
+ const textureSize = { width: 1, height: 1, depthOrArrayLayers: 1 };
+ const texture = (texMismatched ? t.mismatchedDevice : t.device).createTexture({
+ size: textureSize,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+ t.trackForCleanup(texture);
+
+ const isValid = !bufMismatched && !texMismatched;
+
+ if (copyType === 'CopyB2T') {
+ t.testCopyBufferToTexture({ buffer }, { texture }, textureSize, isValid);
+ } else if (copyType === 'CopyT2B') {
+ t.testCopyTextureToBuffer({ texture }, { buffer }, textureSize, isValid);
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/image_copy.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/image_copy.js
new file mode 100644
index 0000000000..3cffa6b3ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/image_copy.js
@@ -0,0 +1,278 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { depthStencilFormatCopyableAspects,
+
+ kTextureFormatInfo,
+ isCompressedTextureFormat } from
+'../../../format_info.js';
+import { align } from '../../../util/math.js';
+
+import { ValidationTest } from '../validation_test.js';
+
+export class ImageCopyTest extends ValidationTest {
+ testRun(
+ textureCopyView,
+ textureDataLayout,
+ size,
+ {
+ method,
+ dataSize,
+ success,
+ submit = false
+
+
+
+
+
+
+
+ })
+ {
+ switch (method) {
+ case 'WriteTexture':{
+ const data = new Uint8Array(dataSize);
+
+ this.expectValidationError(() => {
+ this.device.queue.writeTexture(textureCopyView, data, textureDataLayout, size);
+ }, !success);
+
+ break;
+ }
+ case 'CopyB2T':{
+ const buffer = this.device.createBuffer({
+ size: dataSize,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ this.trackForCleanup(buffer);
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, textureCopyView, size);
+
+ if (submit) {
+ const cmd = encoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, !success);
+ } else {
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+
+ break;
+ }
+ case 'CopyT2B':{
+ if (this.isCompatibility && isCompressedTextureFormat(textureCopyView.texture.format)) {
+ this.skip(
+ 'copyTextureToBuffer is not supported for compressed texture formats in compatibility mode.'
+ );
+ }
+ const buffer = this.device.createBuffer({
+ size: dataSize,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(buffer);
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(textureCopyView, { buffer, ...textureDataLayout }, size);
+
+ if (submit) {
+ const cmd = encoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, !success);
+ } else {
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Creates a texture when all that is needed is an aligned texture given the format and desired
+ * dimensions/origin. The resultant texture guarantees that a copy with the same size and origin
+ * should be possible.
+ */
+ createAlignedTexture(
+ format,
+ size = {
+ width: 1,
+ height: 1,
+ depthOrArrayLayers: 1
+ },
+ origin = { x: 0, y: 0, z: 0 },
+ dimension = '2d')
+ {
+ const info = kTextureFormatInfo[format];
+ const alignedSize = {
+ width: align(Math.max(1, size.width + origin.x), info.blockWidth),
+ height: align(Math.max(1, size.height + origin.y), info.blockHeight),
+ depthOrArrayLayers: Math.max(1, size.depthOrArrayLayers + origin.z)
+ };
+ return this.device.createTexture({
+ size: alignedSize,
+ dimension,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+ }
+
+ testBuffer(
+ buffer,
+ texture,
+ textureDataLayout,
+ size,
+ {
+ method,
+ dataSize,
+ success,
+ submit = true
+
+
+
+
+
+
+
+ })
+ {
+ switch (method) {
+ case 'WriteTexture':{
+ const data = new Uint8Array(dataSize);
+
+ this.expectValidationError(() => {
+ this.device.queue.writeTexture({ texture }, data, textureDataLayout, size);
+ }, !success);
+
+ break;
+ }
+ case 'CopyB2T':{
+ const { encoder, validateFinish, validateFinishAndSubmit } = this.createEncoder('non-pass');
+ encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, { texture }, size);
+
+ if (submit) {
+ // validation error is expected to come from the submit and encoding should succeed
+ validateFinishAndSubmit(true, success);
+ } else {
+ // validation error is expected to come from the encoding
+ validateFinish(success);
+ }
+
+ break;
+ }
+ case 'CopyT2B':{
+ if (this.isCompatibility && isCompressedTextureFormat(texture.format)) {
+ this.skip(
+ 'copyTextureToBuffer is not supported for compressed texture formats in compatibility mode.'
+ );
+ }
+ const { encoder, validateFinish, validateFinishAndSubmit } = this.createEncoder('non-pass');
+ encoder.copyTextureToBuffer({ texture }, { buffer, ...textureDataLayout }, size);
+
+ if (submit) {
+ // validation error is expected to come from the submit and encoding should succeed
+ validateFinishAndSubmit(true, success);
+ } else {
+ // validation error is expected to come from the encoding
+ validateFinish(success);
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+// For testing divisibility by a number we test all the values returned by this function:
+function valuesToTestDivisibilityBy(number) {
+ const values = [];
+ for (let i = 0; i <= 2 * number; ++i) {
+ values.push(i);
+ }
+ values.push(3 * number);
+ return values;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+// This is a helper function used for expanding test parameters for offset alignment, by spec
+export function texelBlockAlignmentTestExpanderForOffset({ format }) {
+ const info = kTextureFormatInfo[format];
+ if (info.depth || info.stencil) {
+ return valuesToTestDivisibilityBy(4);
+ }
+
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].bytesPerBlock);
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on rowsPerImage
+export function texelBlockAlignmentTestExpanderForRowsPerImage({ format }) {
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].blockHeight);
+}
+
+// This is a helper function used for expanding test parameters for texel block alignment tests on origin and size
+export function texelBlockAlignmentTestExpanderForValueToCoordinate({
+ format,
+ coordinateToTest
+}) {
+ switch (coordinateToTest) {
+ case 'x':
+ case 'width':
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].blockWidth);
+
+ case 'y':
+ case 'height':
+ return valuesToTestDivisibilityBy(kTextureFormatInfo[format].blockHeight);
+
+ case 'z':
+ case 'depthOrArrayLayers':
+ return valuesToTestDivisibilityBy(1);
+ }
+}
+
+// This is a helper function used for filtering test parameters
+export function formatCopyableWithMethod({ format, method }) {
+ const info = kTextureFormatInfo[format];
+ if (info.depth || info.stencil) {
+ const supportedAspects = depthStencilFormatCopyableAspects(
+ method,
+ format
+ );
+ return supportedAspects.length > 0;
+ }
+ if (method === 'CopyT2B') {
+ return info.copySrc;
+ } else {
+ return info.copyDst;
+ }
+}
+
+// This is a helper function used for filtering test parameters
+export function getACopyableAspectWithMethod({
+ format,
+ method
+}) {
+ const info = kTextureFormatInfo[format];
+ if (info.depth || info.stencil) {
+ const supportedAspects = depthStencilFormatCopyableAspects(
+ method,
+ format
+ );
+ return supportedAspects[0];
+ }
+ return 'all';
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/layout_related.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/layout_related.spec.js
new file mode 100644
index 0000000000..8246aebf28
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/layout_related.spec.js
@@ -0,0 +1,483 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for the linear data layout of linear data <-> texture copies
+
+TODO check if the tests need to be updated to support aspects of depth-stencil textures`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import { kTextureDimensions } from '../../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kSizedTextureFormats,
+ textureDimensionAndFormatCompatible } from
+'../../../format_info.js';
+import { align } from '../../../util/math.js';
+import {
+ bytesInACompleteRow,
+ dataBytesForCopyOrOverestimate,
+ dataBytesForCopyOrFail,
+ kImageCopyTypes } from
+'../../../util/texture/layout.js';
+
+import {
+ ImageCopyTest,
+ texelBlockAlignmentTestExpanderForOffset,
+ texelBlockAlignmentTestExpanderForRowsPerImage,
+ formatCopyableWithMethod } from
+'./image_copy.js';
+
+export const g = makeTestGroup(ImageCopyTest);
+
+g.test('bound_on_rows_per_image').
+desc(
+ `
+Test that rowsPerImage must be at least the copy height (if defined).
+- for various copy methods
+- for all texture dimensions
+- for various values of rowsPerImage including undefined
+- for various copy heights
+- for various copy depths
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combineWithParams([
+{ dimension: '1d', size: [4, 1, 1] },
+{ dimension: '2d', size: [4, 4, 1] },
+{ dimension: '2d', size: [4, 4, 3] },
+{ dimension: '3d', size: [4, 4, 3] }]
+).
+beginSubcases().
+combine('rowsPerImage', [undefined, 0, 1, 2, 1024]).
+combine('copyHeightInBlocks', [0, 1, 2]).
+combine('copyDepth', [1, 3]).
+unless((p) => p.dimension === '1d' && p.copyHeightInBlocks !== 1).
+unless((p) => p.copyDepth > p.size[2])
+).
+fn((t) => {
+ const { rowsPerImage, copyHeightInBlocks, copyDepth, dimension, size, method } = t.params;
+
+ const format = 'rgba8unorm';
+ const copyHeight = copyHeightInBlocks * kTextureFormatInfo[format].blockHeight;
+
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const layout = { bytesPerRow: 1024, rowsPerImage };
+ const copySize = { width: 0, height: copyHeight, depthOrArrayLayers: copyDepth };
+ const { minDataSizeOrOverestimate, copyValid } = dataBytesForCopyOrOverestimate({
+ layout,
+ format,
+ copySize,
+ method
+ });
+
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSizeOrOverestimate,
+ method,
+ success: copyValid
+ });
+});
+
+g.test('copy_end_overflows_u64').
+desc(
+ `
+Test an error is produced when offset+requiredBytesInCopy overflows GPUSize64.
+- for various copy methods
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+beginSubcases().
+combineWithParams([
+{ bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depthOrArrayLayers: 1, _success: true }, // success case
+{ bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depthOrArrayLayers: 16, _success: false } // bytesPerRow * rowsPerImage * (depthOrArrayLayers - 1) overflows.
+])
+).
+fn((t) => {
+ const { method, bytesPerRow, rowsPerImage, depthOrArrayLayers, _success } = t.params;
+
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ t.testRun(
+ { texture },
+ { bytesPerRow, rowsPerImage },
+ { width: 1, height: 1, depthOrArrayLayers },
+ {
+ dataSize: 10000,
+ method,
+ success: _success
+ }
+ );
+});
+
+g.test('required_bytes_in_copy').
+desc(
+ `
+Test the computation of requiredBytesInCopy by computing the minimum data size for the copy and checking success/error at the boundary.
+- for various copy methods
+- for all formats
+- for all dimensions
+- for various extra bytesPerRow/rowsPerImage
+- for various copy sizes
+- for various offsets in the linear data
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combine('format', kSizedTextureFormats).
+filter(formatCopyableWithMethod).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combineWithParams([
+{ bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 0 }, // no padding
+{ bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 6 }, // rowsPerImage padding
+{ bytesPerRowPadding: 6, rowsPerImagePaddingInBlocks: 0 }, // bytesPerRow padding
+{ bytesPerRowPadding: 15, rowsPerImagePaddingInBlocks: 17 } // both paddings
+]).
+combineWithParams([
+{ copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 5, _offsetMultiplier: 0 }, // standard copy
+{ copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 3, _offsetMultiplier: 11 }, // standard copy, offset > 0
+{ copyWidthInBlocks: 256, copyHeightInBlocks: 3, copyDepth: 2, _offsetMultiplier: 0 }, // copyWidth is 256-aligned
+{ copyWidthInBlocks: 0, copyHeightInBlocks: 4, copyDepth: 5, _offsetMultiplier: 0 }, // empty copy because of width
+{ copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 5, _offsetMultiplier: 0 }, // empty copy because of height
+{ copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 0, _offsetMultiplier: 13 }, // empty copy because of depth, offset > 0
+{ copyWidthInBlocks: 1, copyHeightInBlocks: 4, copyDepth: 5, _offsetMultiplier: 0 }, // copyWidth = 1
+{ copyWidthInBlocks: 3, copyHeightInBlocks: 1, copyDepth: 5, _offsetMultiplier: 15 }, // copyHeight = 1, offset > 0
+{ copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 1, _offsetMultiplier: 0 }, // copyDepth = 1
+{ copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1, _offsetMultiplier: 0 } // copyHeight = 1 and copyDepth = 1
+])
+// The test texture size will be rounded up from the copy size to the next valid texture size.
+// If the format is a depth/stencil format, its copy size must equal to subresource's size.
+// So filter out depth/stencil cases where the rounded-up texture size would be different from the copy size.
+.filter(({ format, copyWidthInBlocks, copyHeightInBlocks, copyDepth }) => {
+ const info = kTextureFormatInfo[format];
+ return (
+ !info.depth && !info.stencil ||
+ copyWidthInBlocks > 0 && copyHeightInBlocks > 0 && copyDepth > 0);
+
+}).
+unless((p) => p.dimension === '1d' && (p.copyHeightInBlocks > 1 || p.copyDepth > 1)).
+expand('offset', (p) => {
+ const info = kTextureFormatInfo[p.format];
+ if (info.depth || info.stencil) {
+ return [p._offsetMultiplier * 4];
+ }
+ return [p._offsetMultiplier * info.color.bytes];
+})
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ offset,
+ bytesPerRowPadding,
+ rowsPerImagePaddingInBlocks,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ format,
+ dimension,
+ method
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ // In the CopyB2T and CopyT2B cases we need to have bytesPerRow 256-aligned,
+ // to make this happen we align the bytesInACompleteRow value and multiply
+ // bytesPerRowPadding by 256.
+ const bytesPerRowAlignment = method === 'WriteTexture' ? 1 : 256;
+ const copyWidth = copyWidthInBlocks * info.blockWidth;
+ const copyHeight = copyHeightInBlocks * info.blockHeight;
+ const rowsPerImage = copyHeight + rowsPerImagePaddingInBlocks * info.blockHeight;
+ const bytesPerRow =
+ align(bytesInACompleteRow(copyWidth, format), bytesPerRowAlignment) +
+ bytesPerRowPadding * bytesPerRowAlignment;
+ const copySize = { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth };
+
+ const layout = { offset, bytesPerRow, rowsPerImage };
+ const minDataSize = dataBytesForCopyOrFail({ layout, format, copySize, method });
+
+ const texture = t.createAlignedTexture(format, copySize, undefined, dimension);
+
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSize,
+ method,
+ success: true
+ });
+
+ if (minDataSize > 0) {
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSize - 1,
+ method,
+ success: false
+ });
+ }
+});
+
+g.test('rows_per_image_alignment').
+desc(
+ `
+Test that rowsPerImage has no alignment constraints.
+- for various copy methods
+- for all sized format
+- for all dimensions
+- for various rowsPerImage
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combine('format', kSizedTextureFormats).
+filter(formatCopyableWithMethod).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+expand('rowsPerImage', texelBlockAlignmentTestExpanderForRowsPerImage)
+// Copy height is info.blockHeight, so rowsPerImage must be equal or greater than it.
+.filter(({ rowsPerImage, format }) => rowsPerImage >= kTextureFormatInfo[format].blockHeight)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { rowsPerImage, format, method } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const size = { width: info.blockWidth, height: info.blockHeight, depthOrArrayLayers: 1 };
+ const texture = t.device.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ t.testRun({ texture }, { bytesPerRow: 256, rowsPerImage }, size, {
+ dataSize: info.bytesPerBlock,
+ method,
+ success: true
+ });
+});
+
+g.test('offset_alignment').
+desc(
+ `
+Test the alignment requirement on the linear data offset (block size, or 4 for depth-stencil).
+- for various copy methods
+- for all sized formats
+- for all dimensions
+- for various linear data offsets
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combine('format', kSizedTextureFormats).
+filter(formatCopyableWithMethod).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+expand('offset', texelBlockAlignmentTestExpanderForOffset)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { format, offset, method } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const size = { width: info.blockWidth, height: info.blockHeight, depthOrArrayLayers: 1 };
+ const texture = t.device.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ let success = false;
+ if (method === 'WriteTexture') success = true;
+ if (info.depth || info.stencil) {
+ if (offset % 4 === 0) success = true;
+ } else {
+ if (offset % info.color.bytes === 0) success = true;
+ }
+
+ t.testRun({ texture }, { offset, bytesPerRow: 256 }, size, {
+ dataSize: offset + info.bytesPerBlock,
+ method,
+ success
+ });
+});
+
+g.test('bound_on_bytes_per_row').
+desc(
+ `
+Test that bytesPerRow, if specified must be big enough for a full copy row.
+- for various copy methods
+- for all sized formats
+- for all dimension
+- for various copy heights
+- for various copy depths
+- for various combinations of bytesPerRow and copy width.
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combine('format', kSizedTextureFormats).
+filter(formatCopyableWithMethod).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combine('copyHeightInBlocks', [1, 2]).
+combine('copyDepth', [1, 2]).
+unless((p) => p.dimension === '1d' && (p.copyHeightInBlocks > 1 || p.copyDepth > 1)).
+expandWithParams((p) => {
+ const info = kTextureFormatInfo[p.format];
+ // We currently have a built-in assumption that for all formats, 128 % bytesPerBlock === 0.
+ // This assumption ensures that all division below results in integers.
+ assert(128 % info.bytesPerBlock === 0);
+ return [
+ // Copying exact fit with aligned bytesPerRow should work.
+ {
+ bytesPerRow: 256,
+ widthInBlocks: 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 256 / info.bytesPerBlock,
+ _success: true
+ },
+ // Copying into smaller texture when padding in bytesPerRow is enough should work unless
+ // it is a depth/stencil typed format.
+ {
+ bytesPerRow: 256,
+ widthInBlocks: 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 256 / info.bytesPerBlock - 1,
+ _success: !(info.stencil || info.depth)
+ },
+ // Unaligned bytesPerRow should not work unless the method is 'WriteTexture'.
+ {
+ bytesPerRow: 128,
+ widthInBlocks: 128 / info.bytesPerBlock,
+ copyWidthInBlocks: 128 / info.bytesPerBlock,
+ _success: p.method === 'WriteTexture'
+ },
+ {
+ bytesPerRow: 384,
+ widthInBlocks: 384 / info.bytesPerBlock,
+ copyWidthInBlocks: 384 / info.bytesPerBlock,
+ _success: p.method === 'WriteTexture'
+ },
+ // When bytesPerRow is smaller than bytesInLastRow copying should fail.
+ {
+ bytesPerRow: 256,
+ widthInBlocks: 2 * 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 2 * 256 / info.bytesPerBlock,
+ _success: false
+ },
+ // When copyHeightInBlocks > 1, bytesPerRow must be specified.
+ {
+ bytesPerRow: undefined,
+ widthInBlocks: 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 256 / info.bytesPerBlock,
+ _success: !(p.copyHeightInBlocks > 1 || p.copyDepth > 1)
+ }];
+
+})
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ method,
+ format,
+ bytesPerRow,
+ widthInBlocks,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ _success
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ // We create an aligned texture using the widthInBlocks which may be different from the
+ // copyWidthInBlocks. This allows us to test scenarios where the two may be different.
+ const texture = t.createAlignedTexture(format, {
+ width: widthInBlocks * info.blockWidth,
+ height: copyHeightInBlocks * info.blockHeight,
+ depthOrArrayLayers: copyDepth
+ });
+
+ const layout = { bytesPerRow, rowsPerImage: copyHeightInBlocks };
+ const copySize = {
+ width: copyWidthInBlocks * info.blockWidth,
+ height: copyHeightInBlocks * info.blockHeight,
+ depthOrArrayLayers: copyDepth
+ };
+ const { minDataSizeOrOverestimate } = dataBytesForCopyOrOverestimate({
+ layout,
+ format,
+ copySize,
+ method
+ });
+
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSizeOrOverestimate,
+ method,
+ success: _success
+ });
+});
+
+g.test('bound_on_offset').
+desc(
+ `
+Test that the offset cannot be larger than the linear data size (even for an empty copy).
+- for various offsets and data sizes
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+beginSubcases().
+combine('offsetInBlocks', [0, 1, 2]).
+combine('dataSizeInBlocks', [0, 1, 2])
+).
+fn((t) => {
+ const { offsetInBlocks, dataSizeInBlocks, method } = t.params;
+
+ const format = 'rgba8unorm';
+ const info = kTextureFormatInfo[format];
+ const offset = offsetInBlocks * info.color.bytes;
+ const dataSize = dataSizeInBlocks * info.color.bytes;
+
+ const texture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const success = offset <= dataSize;
+
+ t.testRun(
+ { texture },
+ { offset, bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize, method, success }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/texture_related.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/texture_related.spec.js
new file mode 100644
index 0000000000..7b535935c3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/image_copy/texture_related.spec.js
@@ -0,0 +1,534 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Texture related validation tests for B2T copy and T2B copy and writeTexture.`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import { kTextureDimensions, kTextureUsages } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import {
+ kColorTextureFormats,
+ kSizedTextureFormats,
+ kTextureFormatInfo,
+ textureDimensionAndFormatCompatible } from
+'../../../format_info.js';
+import { kResourceStates } from '../../../gpu_test.js';
+import { align } from '../../../util/math.js';
+import { virtualMipSize } from '../../../util/texture/base.js';
+import { kImageCopyTypes } from '../../../util/texture/layout.js';
+
+import {
+ ImageCopyTest,
+ texelBlockAlignmentTestExpanderForValueToCoordinate,
+ formatCopyableWithMethod,
+ getACopyableAspectWithMethod } from
+'./image_copy.js';
+
+export const g = makeTestGroup(ImageCopyTest);
+
+g.test('valid').
+desc(
+ `
+Test that the texture must be valid and not destroyed.
+- for all copy methods
+- for all texture states
+- for various dimensions
+`
+).
+params((u) =>
+u //
+.combine('method', kImageCopyTypes).
+combine('textureState', kResourceStates).
+combineWithParams([
+{ dimension: '1d', size: [4, 1, 1] },
+{ dimension: '2d', size: [4, 4, 1] },
+{ dimension: '2d', size: [4, 4, 3] },
+{ dimension: '3d', size: [4, 4, 3] }]
+)
+).
+fn((t) => {
+ const { method, textureState, size, dimension } = t.params;
+
+ const texture = t.createTextureWithState(textureState, {
+ size,
+ dimension,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const success = textureState === 'valid';
+ const submit = textureState !== 'invalid';
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success, submit }
+ );
+});
+
+g.test('texture,device_mismatch').
+desc('Tests the image copies cannot be called with a texture created from another device').
+paramsSubcasesOnly((u) =>
+u.combine('method', kImageCopyTypes).combine('mismatched', [true, false])
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { method, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const texture = sourceDevice.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success: !mismatched }
+ );
+});
+
+g.test('usage').
+desc(
+ `
+The texture must have the appropriate COPY_SRC/COPY_DST usage.
+- for various copy methods
+- for various dimensions
+- for various usages
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combineWithParams([
+{ dimension: '1d', size: [4, 1, 1] },
+{ dimension: '2d', size: [4, 4, 1] },
+{ dimension: '2d', size: [4, 4, 3] },
+{ dimension: '3d', size: [4, 4, 3] }]
+).
+beginSubcases()
+// If usage0 and usage1 are the same, the usage being test is a single usage. Otherwise, it's
+// a combined usage.
+.combine('usage0', kTextureUsages).
+combine('usage1', kTextureUsages)
+// RENDER_ATTACHMENT is not valid with 1d and 3d textures.
+.unless(
+ ({ usage0, usage1, dimension }) =>
+ ((usage0 | usage1) & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && (
+ dimension === '1d' || dimension === '3d')
+)
+).
+fn((t) => {
+ const { usage0, usage1, method, size, dimension } = t.params;
+
+ const usage = usage0 | usage1;
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format: 'rgba8unorm',
+ usage
+ });
+
+ const success =
+ method === 'CopyT2B' ?
+ (usage & GPUTextureUsage.COPY_SRC) !== 0 :
+ (usage & GPUTextureUsage.COPY_DST) !== 0;
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success }
+ );
+});
+
+g.test('sample_count').
+desc(
+ `
+Test that multisampled textures cannot be copied.
+- for various copy methods
+- multisampled or not
+
+Note: we don't test 1D, 2D array and 3D textures because multisample is not supported them.
+`
+).
+params((u) =>
+u //
+.combine('method', kImageCopyTypes).
+beginSubcases().
+combine('sampleCount', [1, 4])
+).
+fn((t) => {
+ const { sampleCount, method } = t.params;
+
+ const texture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ sampleCount,
+ format: 'rgba8unorm',
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const success = sampleCount === 1;
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success }
+ );
+});
+
+g.test('mip_level').
+desc(
+ `
+Test that the mipLevel of the copy must be in range of the texture.
+- for various copy methods
+- for various dimensions
+- for several mipLevelCounts
+- for several target/source mipLevels`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combineWithParams([
+{ dimension: '1d', size: [32, 1, 1] },
+{ dimension: '2d', size: [32, 32, 1] },
+{ dimension: '2d', size: [32, 32, 3] },
+{ dimension: '3d', size: [32, 32, 3] }]
+).
+beginSubcases().
+combine('mipLevelCount', [1, 3, 5]).
+unless((p) => p.dimension === '1d' && p.mipLevelCount !== 1).
+combine('mipLevel', [0, 1, 3, 4])
+).
+fn((t) => {
+ const { mipLevelCount, mipLevel, method, size, dimension } = t.params;
+
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ mipLevelCount,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ const success = mipLevel < mipLevelCount;
+
+ t.testRun(
+ { texture, mipLevel },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success }
+ );
+});
+
+g.test('format').
+desc(
+ `
+Test the copy must be a full subresource if the texture's format is depth/stencil format.
+- for various copy methods
+- for various dimensions
+- for all sized formats
+- for a couple target/source mipLevels
+- for some modifier (or not) for the full copy size
+`
+).
+params((u) =>
+u //
+.combine('method', kImageCopyTypes).
+combineWithParams([
+{ depthOrArrayLayers: 1, dimension: '1d' },
+{ depthOrArrayLayers: 1, dimension: '2d' },
+{ depthOrArrayLayers: 3, dimension: '2d' },
+{ depthOrArrayLayers: 32, dimension: '3d' }]
+).
+combine('format', kSizedTextureFormats).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+filter(formatCopyableWithMethod).
+beginSubcases().
+combine('mipLevel', [0, 2]).
+unless((p) => p.dimension === '1d' && p.mipLevel !== 0).
+combine('copyWidthModifier', [0, -1]).
+combine('copyHeightModifier', [0, -1])
+// If the texture has multiple depth/array slices and it is not a 3D texture, which means it is an array texture,
+// depthModifier is not needed upon the third dimension. Because different layers are different subresources in
+// an array texture. Whether it is a full copy or non-full copy doesn't make sense across different subresources.
+// However, different depth slices on the same mip level are within the same subresource for a 3d texture. So we
+// need to examine depth dimension via copyDepthModifier to determine whether it is a full copy for a 3D texture.
+.expand('copyDepthModifier', ({ dimension: d }) => d === '3d' ? [0, -1] : [0])
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ method,
+ depthOrArrayLayers,
+ dimension,
+ format,
+ mipLevel,
+ copyWidthModifier,
+ copyHeightModifier,
+ copyDepthModifier
+ } = t.params;
+
+ const info = kTextureFormatInfo[format];
+ const size = { width: 32 * info.blockWidth, height: 32 * info.blockHeight, depthOrArrayLayers };
+ if (dimension === '1d') {
+ size.height = 1;
+ }
+
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format,
+ mipLevelCount: dimension === '1d' ? 1 : 5,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ let success = true;
+ if (
+ (info.depth || info.stencil) && (
+ copyWidthModifier !== 0 || copyHeightModifier !== 0 || copyDepthModifier !== 0))
+ {
+ success = false;
+ }
+
+ const levelSize = virtualMipSize(
+ dimension,
+ [size.width, size.height, size.depthOrArrayLayers],
+ mipLevel
+ );
+ const copySize = [
+ levelSize[0] + copyWidthModifier * info.blockWidth,
+ levelSize[1] + copyHeightModifier * info.blockHeight,
+ // Note that compressed format is not supported for 3D textures yet, so there is no info.blockDepth.
+ levelSize[2] + copyDepthModifier];
+
+
+ t.testRun(
+ { texture, mipLevel, aspect: getACopyableAspectWithMethod({ format, method }) },
+ { bytesPerRow: 512, rowsPerImage: 32 },
+ copySize,
+ {
+ dataSize: 512 * 32 * 32,
+ method,
+ success
+ }
+ );
+});
+
+g.test('origin_alignment').
+desc(
+ `
+Test that the texture copy origin must be aligned to the format's block size.
+- for various copy methods
+- for all color formats (depth stencil formats require a full copy)
+- for X, Y and Z coordinates
+- for various values for that coordinate depending on the block size
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes)
+// No need to test depth/stencil formats because its copy origin must be [0, 0, 0], which is already aligned with block size.
+.combine('format', kColorTextureFormats).
+filter(formatCopyableWithMethod).
+combineWithParams([
+{ depthOrArrayLayers: 1, dimension: '1d' },
+{ depthOrArrayLayers: 1, dimension: '2d' },
+{ depthOrArrayLayers: 3, dimension: '2d' },
+{ depthOrArrayLayers: 3, dimension: '3d' }]
+).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combine('coordinateToTest', ['x', 'y', 'z']).
+unless((p) => p.dimension === '1d' && p.coordinateToTest !== 'x').
+expand('valueToCoordinate', texelBlockAlignmentTestExpanderForValueToCoordinate)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { valueToCoordinate, coordinateToTest, format, method, depthOrArrayLayers, dimension } =
+ t.params;
+ const info = kTextureFormatInfo[format];
+ const size = { width: 0, height: 0, depthOrArrayLayers };
+ const origin = { x: 0, y: 0, z: 0 };
+ let success = true;
+
+ origin[coordinateToTest] = valueToCoordinate;
+ switch (coordinateToTest) {
+ case 'x':{
+ success = origin.x % info.blockWidth === 0;
+ break;
+ }
+ case 'y':{
+ success = origin.y % info.blockHeight === 0;
+ break;
+ }
+ }
+
+ const texture = t.createAlignedTexture(format, size, origin, dimension);
+
+ t.testRun({ texture, origin }, { bytesPerRow: 0, rowsPerImage: 0 }, size, {
+ dataSize: 1,
+ method,
+ success
+ });
+});
+
+g.test('size_alignment').
+desc(
+ `
+Test that the copy size must be aligned to the texture's format's block size.
+- for various copy methods
+- for all formats (depth-stencil formats require a full copy)
+- for all texture dimensions
+- for the size's parameters to test (width / height / depth)
+- for various values for that copy size parameters, depending on the block size
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes)
+// No need to test depth/stencil formats because its copy size must be subresource's size, which is already aligned with block size.
+.combine('format', kColorTextureFormats).
+filter(formatCopyableWithMethod).
+combine('dimension', kTextureDimensions).
+filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format)).
+beginSubcases().
+combine('coordinateToTest', ['width', 'height', 'depthOrArrayLayers']).
+unless((p) => p.dimension === '1d' && p.coordinateToTest !== 'width').
+expand('valueToCoordinate', texelBlockAlignmentTestExpanderForValueToCoordinate)
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { valueToCoordinate, coordinateToTest, dimension, format, method } = t.params;
+ const info = kTextureFormatInfo[format];
+ const size = { width: 0, height: 0, depthOrArrayLayers: 0 };
+ const origin = { x: 0, y: 0, z: 0 };
+ let success = true;
+
+ size[coordinateToTest] = valueToCoordinate;
+ switch (coordinateToTest) {
+ case 'width':{
+ success = size.width % info.blockWidth === 0;
+ break;
+ }
+ case 'height':{
+ success = size.height % info.blockHeight === 0;
+ break;
+ }
+ }
+
+ const texture = t.createAlignedTexture(format, size, origin, dimension);
+
+ const bytesPerRow = align(
+ Math.max(1, Math.ceil(size.width / info.blockWidth)) * info.bytesPerBlock,
+ 256
+ );
+ const rowsPerImage = Math.ceil(size.height / info.blockHeight);
+ t.testRun({ texture, origin }, { bytesPerRow, rowsPerImage }, size, {
+ dataSize: 1,
+ method,
+ success
+ });
+});
+
+g.test('copy_rectangle').
+desc(
+ `
+Test that the max corner of the copy rectangle (origin+copySize) must be inside the texture.
+- for various copy methods
+- for all dimensions
+- for the X, Y and Z dimensions
+- for various origin and copy size values (and texture sizes)
+- for various mip levels
+`
+).
+params((u) =>
+u.
+combine('method', kImageCopyTypes).
+combine('dimension', kTextureDimensions).
+beginSubcases().
+combine('originValue', [7, 8]).
+combine('copySizeValue', [7, 8]).
+combine('textureSizeValue', [14, 15]).
+combine('mipLevel', [0, 2]).
+combine('coordinateToTest', [0, 1, 2]).
+unless((p) => p.dimension === '1d' && (p.coordinateToTest !== 0 || p.mipLevel !== 0))
+).
+fn((t) => {
+ const {
+ originValue,
+ copySizeValue,
+ textureSizeValue,
+ mipLevel,
+ coordinateToTest,
+ method,
+ dimension
+ } = t.params;
+ const format = 'rgba8unorm';
+ const info = kTextureFormatInfo[format];
+
+ const origin = [0, 0, 0];
+ const copySize = [0, 0, 0];
+ const textureSize = { width: 16 << mipLevel, height: 16 << mipLevel, depthOrArrayLayers: 16 };
+ if (dimension === '1d') {
+ textureSize.height = 1;
+ textureSize.depthOrArrayLayers = 1;
+ }
+ const success = originValue + copySizeValue <= textureSizeValue;
+
+ origin[coordinateToTest] = originValue;
+ copySize[coordinateToTest] = copySizeValue;
+ switch (coordinateToTest) {
+ case 0:{
+ textureSize.width = textureSizeValue << mipLevel;
+ break;
+ }
+ case 1:{
+ textureSize.height = textureSizeValue << mipLevel;
+ break;
+ }
+ case 2:{
+ textureSize.depthOrArrayLayers =
+ dimension === '3d' ? textureSizeValue << mipLevel : textureSizeValue;
+ break;
+ }
+ }
+
+ const texture = t.device.createTexture({
+ size: textureSize,
+ dimension,
+ mipLevelCount: dimension === '1d' ? 1 : 3,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
+ });
+
+ assert(copySize[0] % info.blockWidth === 0);
+ const bytesPerRow = align(copySize[0] / info.blockWidth, 256);
+ assert(copySize[1] % info.blockHeight === 0);
+ const rowsPerImage = copySize[1] / info.blockHeight;
+ t.testRun({ texture, origin, mipLevel }, { bytesPerRow, rowsPerImage }, copySize, {
+ dataSize: 1,
+ method,
+ success
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/layout_shader_compat.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/layout_shader_compat.spec.js
new file mode 100644
index 0000000000..f579b416d1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/layout_shader_compat.spec.js
@@ -0,0 +1,14 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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}
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+
+import { ValidationTest } from './validation_test.js';
+
+export const g = makeTestGroup(ValidationTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/create.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/create.spec.js
new file mode 100644
index 0000000000..283b92abec
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/create.spec.js
@@ -0,0 +1,34 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for validation in createQuerySet.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kQueryTypes, kMaxQueryCount } from '../../../capability_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('count').
+desc(
+ `
+Tests that create query set with the count for all query types:
+- count {<, =, >} kMaxQueryCount
+- x= {occlusion, timestamp} query
+ `
+).
+params((u) =>
+u.
+combine('type', kQueryTypes).
+beginSubcases().
+combine('count', [0, kMaxQueryCount, kMaxQueryCount + 1])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForQueryTypeOrSkipTestCase(t.params.type);
+}).
+fn((t) => {
+ const { type, count } = t.params;
+
+ t.expectValidationError(() => {
+ t.device.createQuerySet({ type, count });
+ }, count > kMaxQueryCount);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/destroy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/destroy.spec.js
new file mode 100644
index 0000000000..58e8b03d47
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/query_set/destroy.spec.js
@@ -0,0 +1,33 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Destroying a query set more than once is allowed.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('twice').fn((t) => {
+ const qset = t.device.createQuerySet({ type: 'occlusion', count: 1 });
+
+ qset.destroy();
+ qset.destroy();
+});
+
+g.test('invalid_queryset').
+desc('Test that invalid querysets may be destroyed without generating validation errors.').
+fn(async (t) => {
+ t.device.pushErrorScope('validation');
+
+ const invalidQuerySet = t.device.createQuerySet({
+ type: 'occlusion',
+ count: 4097 // 4096 is the limit
+ });
+
+ // Expect error because it's invalid.
+ const error = await t.device.popErrorScope();
+ t.expect(!!error);
+
+ // This line should not generate an error
+ invalidQuerySet.destroy();
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/buffer_mapped.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/buffer_mapped.spec.js
new file mode 100644
index 0000000000..1647ebd351
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/buffer_mapped.spec.js
@@ -0,0 +1,280 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for the map-state of mappable buffers used in submitted command buffers.
+
+Tests every operation that has a dependency on a buffer
+ - writeBuffer
+ - copyB2B {src,dst}
+ - copyB2T
+ - copyT2B
+
+Test those operations against buffers in the following states:
+ - Unmapped
+ - In the process of mapping
+ - mapped
+ - mapped with a mapped range queried
+ - unmapped after mapping
+ - mapped at creation
+
+Also tests every order of operations combination of mapping operations and command recording
+operations to ensure the mapping state is only considered when a command buffer is submitted.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ValidationTest } from '../validation_test.js';
+
+class F extends ValidationTest {
+ async runBufferDependencyTest(usage, callback) {
+ const bufferDesc = {
+ size: 8,
+ usage,
+ mappedAtCreation: false
+ };
+
+ const mapMode = usage & GPUBufferUsage.MAP_READ ? GPUMapMode.READ : GPUMapMode.WRITE;
+
+ // Create a mappable buffer, and one that will remain unmapped for comparison.
+ const mappableBuffer = this.device.createBuffer(bufferDesc);
+ const unmappedBuffer = this.device.createBuffer(bufferDesc);
+
+ // Run the given operation before the buffer is mapped. Should succeed.
+ callback(mappableBuffer);
+
+ // Map the buffer
+ const mapPromise = mappableBuffer.mapAsync(mapMode);
+
+ // Run the given operation while the buffer is in the process of mapping. Should fail.
+ this.expectValidationError(() => {
+ callback(mappableBuffer);
+ });
+
+ // Run on a different, unmapped buffer. Should succeed.
+ callback(unmappedBuffer);
+
+ await mapPromise;
+
+ // Run the given operation when the buffer is finished mapping with no getMappedRange. Should fail.
+ this.expectValidationError(() => {
+ callback(mappableBuffer);
+ });
+
+ // Run on a different, unmapped buffer. Should succeed.
+ callback(unmappedBuffer);
+
+ // Run the given operation when the buffer is mapped with getMappedRange. Should fail.
+ mappableBuffer.getMappedRange();
+ this.expectValidationError(() => {
+ callback(mappableBuffer);
+ });
+
+ // Unmap the buffer and run the operation. Should succeed.
+ mappableBuffer.unmap();
+ callback(mappableBuffer);
+
+ // Create a buffer that's mappedAtCreation.
+ bufferDesc.mappedAtCreation = true;
+ const mappedBuffer = this.device.createBuffer(bufferDesc);
+
+ // Run the operation with the mappedAtCreation buffer. Should fail.
+ this.expectValidationError(() => {
+ callback(mappedBuffer);
+ });
+
+ // Run on a different, unmapped buffer. Should succeed.
+ callback(unmappedBuffer);
+
+ // Unmap the mappedAtCreation buffer and run the operation. Should succeed.
+ mappedBuffer.unmap();
+ callback(mappedBuffer);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('writeBuffer').
+desc(`Test that an outstanding mapping will prevent writeBuffer calls.`).
+fn(async (t) => {
+ const data = new Uint32Array([42]);
+
+ await t.runBufferDependencyTest(
+ GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
+ (buffer) => {
+ t.queue.writeBuffer(buffer, 0, data);
+ }
+ );
+});
+
+g.test('copyBufferToBuffer').
+desc(
+ `
+ Test that an outstanding mapping will prevent copyBufferToTexture commands from submitting,
+ both when used as the source and destination.`
+).
+fn(async (t) => {
+ const sourceBuffer = t.device.createBuffer({
+ size: 8,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+
+ const destBuffer = t.device.createBuffer({
+ size: 8,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ await t.runBufferDependencyTest(
+ GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
+ (buffer) => {
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.copyBufferToBuffer(buffer, 0, destBuffer, 0, 4);
+ t.queue.submit([commandEncoder.finish()]);
+ }
+ );
+
+ await t.runBufferDependencyTest(
+ GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
+ (buffer) => {
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.copyBufferToBuffer(sourceBuffer, 0, buffer, 0, 4);
+ t.queue.submit([commandEncoder.finish()]);
+ }
+ );
+});
+
+g.test('copyBufferToTexture').
+desc(
+ `Test that an outstanding mapping will prevent copyBufferToTexture commands from submitting.`
+).
+fn(async (t) => {
+ const size = { width: 1, height: 1 };
+
+ const texture = t.device.createTexture({
+ size,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ await t.runBufferDependencyTest(
+ GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
+ (buffer) => {
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.copyBufferToTexture({ buffer }, { texture }, size);
+ t.queue.submit([commandEncoder.finish()]);
+ }
+ );
+});
+
+g.test('copyTextureToBuffer').
+desc(
+ `Test that an outstanding mapping will prevent copyTextureToBuffer commands from submitting.`
+).
+fn(async (t) => {
+ const size = { width: 1, height: 1 };
+
+ const texture = t.device.createTexture({
+ size,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC
+ });
+
+ await t.runBufferDependencyTest(
+ GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
+ (buffer) => {
+ const commandEncoder = t.device.createCommandEncoder();
+ commandEncoder.copyTextureToBuffer({ texture }, { buffer }, size);
+ t.queue.submit([commandEncoder.finish()]);
+ }
+ );
+});
+
+g.test('map_command_recording_order').
+desc(
+ `
+Test that the order of mapping a buffer relative to when commands are recorded that use it
+ does not matter, as long as the buffer is unmapped when the commands are submitted.
+ `
+).
+paramsSubcasesOnly([
+{
+ order: ['record', 'map', 'unmap', 'finish', 'submit'],
+ mappedAtCreation: false,
+ _shouldError: false
+},
+{
+ order: ['record', 'map', 'finish', 'unmap', 'submit'],
+ mappedAtCreation: false,
+ _shouldError: false
+},
+{
+ order: ['record', 'finish', 'map', 'unmap', 'submit'],
+ mappedAtCreation: false,
+ _shouldError: false
+},
+{
+ order: ['map', 'record', 'unmap', 'finish', 'submit'],
+ mappedAtCreation: false,
+ _shouldError: false
+},
+{
+ order: ['map', 'record', 'finish', 'unmap', 'submit'],
+ mappedAtCreation: false,
+ _shouldError: false
+},
+{
+ order: ['map', 'record', 'finish', 'submit', 'unmap'],
+ mappedAtCreation: false,
+ _shouldError: true
+},
+{
+ order: ['record', 'map', 'finish', 'submit', 'unmap'],
+ mappedAtCreation: false,
+ _shouldError: true
+},
+{
+ order: ['record', 'finish', 'map', 'submit', 'unmap'],
+ mappedAtCreation: false,
+ _shouldError: true
+},
+{ order: ['record', 'unmap', 'finish', 'submit'], mappedAtCreation: true, _shouldError: false },
+{ order: ['record', 'finish', 'unmap', 'submit'], mappedAtCreation: true, _shouldError: false },
+{ order: ['record', 'finish', 'submit', 'unmap'], mappedAtCreation: true, _shouldError: true }]
+).
+fn(async (t) => {
+ const { order, mappedAtCreation, _shouldError: shouldError } = t.params;
+
+ const buffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation
+ });
+
+ const targetBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_DST
+ });
+
+ const commandEncoder = t.device.createCommandEncoder();
+ let commandBuffer;
+
+ const steps = {
+ record: () => {
+ commandEncoder.copyBufferToBuffer(buffer, 0, targetBuffer, 0, 4);
+ },
+ map: async () => {
+ await buffer.mapAsync(GPUMapMode.WRITE);
+ },
+ unmap: () => {
+ buffer.unmap();
+ },
+ finish: () => {
+ commandBuffer = commandEncoder.finish();
+ },
+ submit: () => {
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, shouldError);
+ }
+ };
+
+ for (const op of order) {
+ await steps[op]();
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js
new file mode 100644
index 0000000000..a66b9a7747
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js
@@ -0,0 +1,816 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyExternalImageToTexture Validation Tests in Queue.
+Note that we don't need to add tests on the destination texture dimension as currently we require
+the destination texture should have RENDER_ATTACHMENT usage, which is only allowed to be used on 2D
+textures.
+`;import {
+ getResourcePath,
+ getCrossOriginResourcePath } from
+'../../../../../common/framework/resources.js';
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { raceWithRejectOnTimeout, unreachable, assert } from '../../../../../common/util/util.js';
+import { kTextureUsages } from '../../../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kTextureFormats,
+ kValidTextureFormatsForCopyE2T } from
+'../../../../format_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import {
+
+ createCanvas,
+ createOnscreenCanvas,
+ createOffscreenCanvas } from
+'../../../../util/create_elements.js';
+import { ValidationTest } from '../../validation_test.js';
+
+const kDefaultBytesPerPixel = 4; // using 'bgra8unorm' or 'rgba8unorm'
+const kDefaultWidth = 32;
+const kDefaultHeight = 32;
+const kDefaultDepth = 1;
+const kDefaultMipLevelCount = 6;
+
+function computeMipMapSize(width, height, mipLevel) {
+ return {
+ mipWidth: Math.max(width >> mipLevel, 1),
+ mipHeight: Math.max(height >> mipLevel, 1)
+ };
+}
+
+
+
+
+
+
+
+
+
+// Helper function to generate copySize for src OOB test
+function generateCopySizeForSrcOOB({ srcOrigin }) {
+ // OOB origin fails even with no-op copy.
+ if (srcOrigin.x > kDefaultWidth || srcOrigin.y > kDefaultHeight) {
+ return [{ width: 0, height: 0, depthOrArrayLayers: 0 }];
+ }
+
+ const justFitCopySize = {
+ width: kDefaultWidth - srcOrigin.x,
+ height: kDefaultHeight - srcOrigin.y,
+ depthOrArrayLayers: 1
+ };
+
+ return [
+ justFitCopySize, // correct size, maybe no-op copy.
+ {
+ width: justFitCopySize.width + 1,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in width
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height + 1,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in height
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers + 1
+ } // OOB in depthOrArrayLayers
+ ];
+}
+
+// Helper function to generate dst origin value based on mipLevel.
+function generateDstOriginValue({ mipLevel }) {
+ const origin = computeMipMapSize(kDefaultWidth, kDefaultHeight, mipLevel);
+
+ return [
+ { x: 0, y: 0, z: 0 },
+ { x: origin.mipWidth - 1, y: 0, z: 0 },
+ { x: 0, y: origin.mipHeight - 1, z: 0 },
+ { x: origin.mipWidth, y: 0, z: 0 },
+ { x: 0, y: origin.mipHeight, z: 0 },
+ { x: 0, y: 0, z: kDefaultDepth },
+ { x: origin.mipWidth + 1, y: 0, z: 0 },
+ { x: 0, y: origin.mipHeight + 1, z: 0 },
+ { x: 0, y: 0, z: kDefaultDepth + 1 }];
+
+}
+
+// Helper function to generate copySize for dst OOB test
+function generateCopySizeForDstOOB({ mipLevel, dstOrigin }) {
+ const dstMipMapSize = computeMipMapSize(kDefaultWidth, kDefaultHeight, mipLevel);
+
+ // OOB origin fails even with no-op copy.
+ if (
+ dstOrigin.x > dstMipMapSize.mipWidth ||
+ dstOrigin.y > dstMipMapSize.mipHeight ||
+ dstOrigin.z > kDefaultDepth)
+ {
+ return [{ width: 0, height: 0, depthOrArrayLayers: 0 }];
+ }
+
+ const justFitCopySize = {
+ width: dstMipMapSize.mipWidth - dstOrigin.x,
+ height: dstMipMapSize.mipHeight - dstOrigin.y,
+ depthOrArrayLayers: kDefaultDepth - dstOrigin.z
+ };
+
+ return [
+ justFitCopySize,
+ {
+ width: justFitCopySize.width + 1,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in width
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height + 1,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in height
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers + 1
+ } // OOB in depthOrArrayLayers
+ ];
+}
+
+class CopyExternalImageToTextureTest extends ValidationTest {
+ onlineCrossOriginUrl = 'https://raw.githubusercontent.com/gpuweb/gpuweb/main/logo/webgpu.png';
+
+ getImageData(width, height) {
+ if (typeof ImageData === 'undefined') {
+ this.skip('ImageData is not supported.');
+ }
+
+ const pixelSize = kDefaultBytesPerPixel * width * height;
+ const imagePixels = new Uint8ClampedArray(pixelSize);
+ return new ImageData(imagePixels, width, height);
+ }
+
+ getCanvasWithContent(
+ canvasType,
+ width,
+ height,
+ content)
+ {
+ const canvas = createCanvas(this, canvasType, 1, 1);
+ const ctx = canvas.getContext('2d');
+ switch (canvasType) {
+ case 'onscreen':
+ assert(ctx instanceof CanvasRenderingContext2D);
+ break;
+ case 'offscreen':
+ assert(ctx instanceof OffscreenCanvasRenderingContext2D);
+ break;
+ }
+ ctx.drawImage(content, 0, 0);
+
+ return canvas;
+ }
+
+ createImageBitmap(image) {
+ if (typeof createImageBitmap === 'undefined') {
+ this.skip('Creating ImageBitmaps is not supported.');
+ }
+ return createImageBitmap(image);
+ }
+
+ runTest(
+ imageBitmapCopyView,
+ textureCopyView,
+ copySize,
+ validationScopeSuccess,
+ exceptionName)
+ {
+ // copyExternalImageToTexture will generate two types of errors. One is synchronous exceptions;
+ // the other is asynchronous validation error scope errors.
+ if (exceptionName) {
+ this.shouldThrow(exceptionName, () => {
+ this.device.queue.copyExternalImageToTexture(
+ imageBitmapCopyView,
+ textureCopyView,
+ copySize
+ );
+ });
+ } else {
+ this.expectValidationError(() => {
+ this.device.queue.copyExternalImageToTexture(
+ imageBitmapCopyView,
+ textureCopyView,
+ copySize
+ );
+ }, !validationScopeSuccess);
+ }
+ }
+}
+
+export const g = makeTestGroup(CopyExternalImageToTextureTest);
+
+g.test('source_image,crossOrigin').
+desc(
+ `
+ Test contents of source image is [clean, cross-origin].
+
+ Load crossOrigin image or same origin image and init the source
+ images.
+
+ Check whether 'SecurityError' is generated when source image is not origin clean.
+ `
+).
+params((u) =>
+u //
+.combine('sourceImage', ['canvas', 'offscreenCanvas', 'imageBitmap']).
+combine('isOriginClean', [true, false]).
+beginSubcases().
+combine('contentFrom', ['image', 'imageBitmap', 'canvas', 'offscreenCanvas']).
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { sourceImage, isOriginClean, contentFrom, copySize } = t.params;
+ if (typeof document === 'undefined') {
+ t.skip('DOM is not available to create an image element.');
+ }
+
+ const crossOriginUrl = getCrossOriginResourcePath('webgpu.png', t.onlineCrossOriginUrl);
+ const originCleanUrl = getResourcePath('webgpu.png');
+ const img = document.createElement('img');
+ img.src = isOriginClean ? originCleanUrl : crossOriginUrl;
+
+ // Load image
+ const timeout_ms = 5000;
+ try {
+ await raceWithRejectOnTimeout(img.decode(), timeout_ms, 'load image timeout');
+ } catch (e) {
+ if (isOriginClean) {
+ throw e;
+ } else {
+ t.skip('Cannot load cross origin image in time');
+ return;
+ }
+ }
+
+ // The externalImage contents can be updated by:
+ // - decoded image element
+ // - canvas/offscreenCanvas with image draw on it.
+ // - imageBitmap created with the image.
+ // Test covers all of these cases to ensure origin clean checks works.
+ let source;
+ switch (contentFrom) {
+ case 'image':{
+ source = img;
+ break;
+ }
+ case 'imageBitmap':{
+ source = await t.createImageBitmap(img);
+ break;
+ }
+ case 'canvas':
+ case 'offscreenCanvas':{
+ const canvasType = contentFrom === 'offscreenCanvas' ? 'offscreen' : 'onscreen';
+ source = t.getCanvasWithContent(canvasType, 1, 1, img);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ // Update the externalImage content with source.
+ let externalImage;
+ switch (sourceImage) {
+ case 'imageBitmap':{
+ externalImage = await t.createImageBitmap(source);
+ break;
+ }
+ case 'canvas':
+ case 'offscreenCanvas':{
+ const canvasType = contentFrom === 'offscreenCanvas' ? 'offscreen' : 'onscreen';
+ externalImage = t.getCanvasWithContent(canvasType, 1, 1, source);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.runTest(
+ { source: externalImage },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ isOriginClean ? '' : 'SecurityError'
+ );
+});
+
+g.test('source_imageBitmap,state').
+desc(
+ `
+ Test ImageBitmap as source image in state [valid, closed].
+
+ Call imageBitmap.close() to transfer the imageBitmap into
+ 'closed' state.
+
+ Check whether 'InvalidStateError' is generated when ImageBitmap is
+ closed.
+ `
+).
+params((u) =>
+u //
+.combine('closed', [false, true]).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { closed, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ if (closed) imageBitmap.close();
+
+ t.runTest(
+ { source: imageBitmap },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ closed ? 'InvalidStateError' : ''
+ );
+});
+
+g.test('source_canvas,state').
+desc(
+ `
+ Test HTMLCanvasElement as source image in state
+ [nocontext, 'placeholder-nocontext', 'placeholder-hascontext', valid].
+
+ Nocontext means using a canvas without any context as copy param.
+
+ Call 'transferControlToOffscreen' on HTMLCanvasElement will cause the
+ canvas control right transfer. And this canvas is in state 'placeholder'
+ Whether getContext in new generated offscreenCanvas won't affect the origin
+ canvas state.
+
+
+ Check whether 'OperationError' is generated when HTMLCanvasElement has no
+ context.
+
+ Check whether 'InvalidStateError' is generated when HTMLCanvasElement is
+ in 'placeholder' state.
+ `
+).
+params((u) =>
+u //
+.combine('state', ['nocontext', 'placeholder-nocontext', 'placeholder-hascontext', 'valid']).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn((t) => {
+ const { state, copySize } = t.params;
+ const canvas = createOnscreenCanvas(t, 1, 1);
+ if (typeof canvas.transferControlToOffscreen === 'undefined') {
+ t.skip("Browser doesn't support HTMLCanvasElement.transferControlToOffscreen");
+ return;
+ }
+
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let exceptionName = '';
+
+ switch (state) {
+ case 'nocontext':{
+ exceptionName = 'OperationError';
+ break;
+ }
+ case 'placeholder-nocontext':{
+ canvas.transferControlToOffscreen();
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'placeholder-hascontext':{
+ const offscreenCanvas = canvas.transferControlToOffscreen();
+ t.tryTrackForCleanup(offscreenCanvas.getContext('webgl'));
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'valid':{
+ assert(canvas.getContext('2d') !== null);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ t.runTest(
+ { source: canvas },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ exceptionName
+ );
+});
+
+g.test('source_offscreenCanvas,state').
+desc(
+ `
+ Test OffscreenCanvas as source image in state [valid, detached].
+
+ Nocontext means using a canvas without any context as copy param.
+
+ Transfer OffscreenCanvas with MessageChannel will detach the OffscreenCanvas.
+
+ Check whether 'OperationError' is generated when HTMLCanvasElement has no
+ context.
+
+ Check whether 'InvalidStateError' is generated when OffscreenCanvas is
+ detached.
+ `
+).
+params((u) =>
+u //
+.combine('state', ['nocontext', 'detached-nocontext', 'detached-hascontext', 'valid']).
+beginSubcases().
+combine('getContextInOffscreenCanvas', [false, true]).
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { state, copySize } = t.params;
+ const offscreenCanvas = createOffscreenCanvas(t, 1, 1);
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let exceptionName = '';
+ switch (state) {
+ case 'nocontext':{
+ exceptionName = 'OperationError';
+ break;
+ }
+ case 'detached-nocontext':{
+ const messageChannel = new MessageChannel();
+ messageChannel.port1.postMessage(offscreenCanvas, [offscreenCanvas]);
+
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'detached-hascontext':{
+ const messageChannel = new MessageChannel();
+ const port2FirstMessage = new Promise((resolve) => {
+ messageChannel.port2.onmessage = (m) => resolve(m);
+ });
+
+ messageChannel.port1.postMessage(offscreenCanvas, [offscreenCanvas]);
+
+ const receivedOffscreenCanvas = await port2FirstMessage;
+ t.tryTrackForCleanup(receivedOffscreenCanvas.data.getContext('webgl'));
+
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'valid':{
+ offscreenCanvas.getContext('webgl');
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ t.runTest(
+ { source: offscreenCanvas },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ exceptionName
+ );
+});
+
+g.test('destination_texture,state').
+desc(
+ `
+ Test dst texture is [valid, invalid, destroyed].
+
+ Check that an error is generated when texture is an error texture.
+ Check that an error is generated when texture is in destroyed state.
+ `
+).
+params((u) =>
+u //
+.combine('state', kResourceStates).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { state, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.createTextureWithState(state);
+
+ t.runTest({ source: imageBitmap }, { texture: dstTexture }, copySize, state === 'valid');
+});
+
+g.test('destination_texture,device_mismatch').
+desc(
+ 'Tests copyExternalImageToTexture cannot be called with a destination texture created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn(async (t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+ const copySize = { width: 1, height: 1, depthOrArrayLayers: 1 };
+
+ const texture = sourceDevice.createTexture({
+ size: copySize,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+
+ t.runTest({ source: imageBitmap }, { texture }, copySize, !mismatched);
+});
+
+g.test('destination_texture,usage').
+desc(
+ `
+ Test dst texture usages
+
+ Check that an error is generated when texture is created without usage COPY_DST | RENDER_ATTACHMENT.
+ `
+).
+params((u) =>
+u //
+.combine('usage', kTextureUsages).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { usage, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage
+ });
+
+ t.runTest(
+ { source: imageBitmap },
+ { texture: dstTexture },
+ copySize,
+ !!(usage & GPUTextureUsage.COPY_DST && usage & GPUTextureUsage.RENDER_ATTACHMENT)
+ );
+});
+
+g.test('destination_texture,sample_count').
+desc(
+ `
+ Test dst texture sample count.
+
+ Check that an error is generated when sample count it not 1.
+ `
+).
+params((u) =>
+u //
+.combine('sampleCount', [1, 4]).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { sampleCount, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ sampleCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.runTest({ source: imageBitmap }, { texture: dstTexture }, copySize, sampleCount === 1);
+});
+
+g.test('destination_texture,mipLevel').
+desc(
+ `
+ Test dst mipLevel.
+
+ Check that an error is generated when mipLevel is too large.
+ `
+).
+params((u) =>
+u //
+.combine('mipLevel', [0, kDefaultMipLevelCount - 1, kDefaultMipLevelCount]).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { mipLevel, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: kDefaultWidth, height: kDefaultHeight, depthOrArrayLayers: kDefaultDepth },
+ mipLevelCount: kDefaultMipLevelCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.runTest(
+ { source: imageBitmap },
+ { texture: dstTexture, mipLevel },
+ copySize,
+ mipLevel < kDefaultMipLevelCount
+ );
+});
+
+g.test('destination_texture,format').
+desc(
+ `
+ Test dst texture format.
+
+ Check that an error is generated when texture format is not valid.
+ `
+).
+params((u) =>
+u.
+combine('format', kTextureFormats).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn(async (t) => {
+ const { format, copySize } = t.params;
+
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+
+ // createTexture with all possible texture format may have validation error when using
+ // compressed texture format.
+ t.device.pushErrorScope('validation');
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ void t.device.popErrorScope();
+
+ const success = kValidTextureFormatsForCopyE2T.includes(format);
+
+ t.runTest({ source: imageBitmap }, { texture: dstTexture }, copySize, success);
+});
+
+g.test('OOB,source').
+desc(
+ `
+ Test source image origin and copy size
+
+ Check that an error is generated when source.externalImage.origin + copySize is too large.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('srcOrigin', [
+{ x: 0, y: 0 }, // origin is on top-left
+{ x: kDefaultWidth - 1, y: 0 }, // x near the border
+{ x: 0, y: kDefaultHeight - 1 }, // y is near the border
+{ x: kDefaultWidth, y: kDefaultHeight }, // origin is on bottom-right
+{ x: kDefaultWidth + 1, y: 0 }, // x is too large
+{ x: 0, y: kDefaultHeight + 1 } // y is too large
+]).
+expand('copySize', generateCopySizeForSrcOOB)
+).
+fn(async (t) => {
+ const { srcOrigin, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(kDefaultWidth, kDefaultHeight));
+ const dstTexture = t.device.createTexture({
+ size: {
+ width: kDefaultWidth + 1,
+ height: kDefaultHeight + 1,
+ depthOrArrayLayers: kDefaultDepth
+ },
+ mipLevelCount: kDefaultMipLevelCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let success = true;
+
+ if (
+ srcOrigin.x + copySize.width > kDefaultWidth ||
+ srcOrigin.y + copySize.height > kDefaultHeight ||
+ copySize.depthOrArrayLayers > 1)
+ {
+ success = false;
+ }
+
+ t.runTest(
+ { source: imageBitmap, origin: srcOrigin },
+ { texture: dstTexture },
+ copySize,
+ true,
+ success ? '' : 'OperationError'
+ );
+});
+
+g.test('OOB,destination').
+desc(
+ `
+ Test dst texture copy origin and copy size
+
+ Check that an error is generated when destination.texture.origin + copySize is too large.
+ Check that 'OperationError' is generated when copySize.depth is larger than 1.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('mipLevel', [0, 1, kDefaultMipLevelCount - 2]).
+expand('dstOrigin', generateDstOriginValue).
+expand('copySize', generateCopySizeForDstOOB)
+).
+fn(async (t) => {
+ const { mipLevel, dstOrigin, copySize } = t.params;
+
+ const imageBitmap = await t.createImageBitmap(
+ t.getImageData(kDefaultWidth + 1, kDefaultHeight + 1)
+ );
+ const dstTexture = t.device.createTexture({
+ size: {
+ width: kDefaultWidth,
+ height: kDefaultHeight,
+ depthOrArrayLayers: kDefaultDepth
+ },
+ format: 'bgra8unorm',
+ mipLevelCount: kDefaultMipLevelCount,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let success = true;
+ let hasOperationError = false;
+ const dstMipMapSize = computeMipMapSize(kDefaultWidth, kDefaultHeight, mipLevel);
+
+ if (
+ copySize.depthOrArrayLayers > 1 ||
+ dstOrigin.x + copySize.width > dstMipMapSize.mipWidth ||
+ dstOrigin.y + copySize.height > dstMipMapSize.mipHeight ||
+ dstOrigin.z + copySize.depthOrArrayLayers > kDefaultDepth)
+ {
+ success = false;
+ }
+ if (copySize.depthOrArrayLayers > 1) {
+ hasOperationError = true;
+ }
+
+ t.runTest(
+ { source: imageBitmap },
+ {
+ texture: dstTexture,
+ mipLevel,
+ origin: dstOrigin
+ },
+ copySize,
+ success,
+ hasOperationError ? 'OperationError' : ''
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/buffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/buffer.spec.js
new file mode 100644
index 0000000000..aa740c9200
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/buffer.spec.js
@@ -0,0 +1,296 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests using a destroyed buffer on a queue.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('writeBuffer').
+desc(
+ `
+Tests that using a destroyed buffer in writeBuffer fails.
+- x= {destroyed, not destroyed (control case)}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const buffer = t.trackForCleanup(
+ t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_DST
+ })
+ );
+
+ if (destroyed) {
+ buffer.destroy();
+ }
+
+ t.expectValidationError(() => t.queue.writeBuffer(buffer, 0, new Uint8Array(4)), destroyed);
+});
+
+g.test('copyBufferToBuffer').
+desc(
+ `
+Tests that using a destroyed buffer in copyBufferToBuffer fails.
+- x= {not destroyed (control case), src destroyed, dst destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', ['none', 'src', 'dst', 'both'])).
+fn((t) => {
+ const src = t.trackForCleanup(
+ t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_SRC })
+ );
+ const dst = t.trackForCleanup(
+ t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToBuffer(src, 0, dst, 0, dst.size);
+ const commandBuffer = encoder.finish();
+
+ let shouldError = true;
+ switch (t.params.destroyed) {
+ case 'none':
+ shouldError = false;
+ break;
+ case 'src':
+ src.destroy();
+ break;
+ case 'dst':
+ dst.destroy();
+ break;
+ case 'both':
+ src.destroy();
+ dst.destroy();
+ break;
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, shouldError);
+});
+
+g.test('copyBufferToTexture').
+desc(
+ `
+Tests that using a destroyed buffer in copyBufferToTexture fails.
+- x= {not destroyed (control case), src destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const buffer = t.trackForCleanup(
+ t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_SRC })
+ );
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture({ buffer }, { texture }, [1, 1, 1]);
+ const commandBuffer = encoder.finish();
+
+ if (destroyed) {
+ buffer.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('copyTextureToBuffer').
+desc(
+ `
+Tests that using a destroyed buffer in copyTextureToBuffer fails.
+- x= {not destroyed (control case), dst destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC
+ })
+ );
+ const buffer = t.trackForCleanup(
+ t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToBuffer({ texture }, { buffer }, [1, 1, 1]);
+ const commandBuffer = encoder.finish();
+
+ if (destroyed) {
+ buffer.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('setBindGroup').
+desc(
+ `
+Tests that using a destroyed buffer referenced by a bindGroup set with setBindGroup fails
+- x= {not destroyed (control case), destroyed}
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('destroyed', [false, true]).
+combine('encoderType', ['compute pass', 'render pass', 'render bundle'])
+).
+fn((t) => {
+ const { destroyed, encoderType } = t.params;
+ const { device } = t;
+ const buffer = t.trackForCleanup(
+ t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE | GPUShaderStage.VERTEX,
+ buffer: {}
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource: { buffer } }]
+ });
+
+ const { encoder, finish } = t.createEncoder(encoderType);
+ encoder.setBindGroup(0, bindGroup);
+ const commandBuffer = finish();
+
+ if (destroyed) {
+ buffer.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('setVertexBuffer').
+desc(
+ `
+Tests that using a destroyed buffer referenced in a render pass fails
+- x= {not destroyed (control case), destroyed}
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('destroyed', [false, true]).
+combine('encoderType', ['render pass', 'render bundle'])
+).
+fn((t) => {
+ const { destroyed, encoderType } = t.params;
+ const vertexBuffer = t.trackForCleanup(
+ t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.VERTEX
+ })
+ );
+
+ const { encoder, finish } = t.createEncoder(encoderType);
+ encoder.setVertexBuffer(0, vertexBuffer);
+ const commandBuffer = finish();
+
+ if (destroyed) {
+ vertexBuffer.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('setIndexBuffer').
+desc(
+ `
+Tests that using a destroyed buffer referenced in a render pass fails
+- x= {not destroyed (control case), destroyed}
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('destroyed', [false, true]).
+combine('encoderType', ['render pass', 'render bundle'])
+).
+fn((t) => {
+ const { destroyed, encoderType } = t.params;
+ const indexBuffer = t.trackForCleanup(
+ t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ })
+ );
+
+ const { encoder, finish } = t.createEncoder(encoderType);
+ encoder.setIndexBuffer(indexBuffer, 'uint16');
+ const commandBuffer = finish();
+
+ if (destroyed) {
+ indexBuffer.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('resolveQuerySet').
+desc(
+ `
+Tests that using a destroyed buffer referenced via resolveQuerySet fails
+- x= {not destroyed (control case), destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const querySet = t.trackForCleanup(
+ t.device.createQuerySet({
+ type: 'occlusion',
+ count: 1
+ })
+ );
+ const querySetBuffer = t.trackForCleanup(
+ t.device.createBuffer({
+ size: 8,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.resolveQuerySet(querySet, 0, 1, querySetBuffer, 0);
+ const commandBuffer = encoder.finish();
+
+ if (destroyed) {
+ querySetBuffer.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/query_set.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/query_set.spec.js
new file mode 100644
index 0000000000..606a0a7a03
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/query_set.spec.js
@@ -0,0 +1,63 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests using a destroyed query set on a queue.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('beginOcclusionQuery').
+desc(
+ `
+Tests that use a destroyed query set in occlusion query on render pass encoder.
+- x= {destroyed, not destroyed (control case)}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('querySetState', ['valid', 'destroyed'])).
+fn((t) => {
+ const occlusionQuerySet = t.createQuerySetWithState(t.params.querySetState);
+
+ const encoder = t.createEncoder('render pass', { occlusionQuerySet });
+ encoder.encoder.beginOcclusionQuery(0);
+ encoder.encoder.endOcclusionQuery();
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+});
+
+g.test('writeTimestamp').
+desc(
+ `
+Tests that use a destroyed query set in writeTimestamp on {non-pass, compute, render} encoder.
+- x= {destroyed, not destroyed (control case)}
+ `
+).
+params((u) => u.beginSubcases().combine('querySetState', ['valid', 'destroyed'])).
+beforeAllSubcases((t) => t.selectDeviceOrSkipTestCase('timestamp-query')).
+fn((t) => {
+ const querySet = t.createQuerySetWithState(t.params.querySetState, {
+ type: 'timestamp',
+ count: 2
+ });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.writeTimestamp(querySet, 0);
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+});
+
+g.test('resolveQuerySet').
+desc(
+ `
+Tests that use a destroyed query set in resolveQuerySet.
+- x= {destroyed, not destroyed (control case)}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('querySetState', ['valid', 'destroyed'])).
+fn((t) => {
+ const querySet = t.createQuerySetWithState(t.params.querySetState);
+
+ const buffer = t.device.createBuffer({ size: 8, usage: GPUBufferUsage.QUERY_RESOLVE });
+
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder.resolveQuerySet(querySet, 0, 1, buffer, 0);
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/texture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/texture.spec.js
new file mode 100644
index 0000000000..eb32232b51
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/destroyed/texture.spec.js
@@ -0,0 +1,294 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests using a destroyed texture on a queue.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { unreachable } from '../../../../../common/util/util.js';
+import { ValidationTest } from '../../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('writeTexture').
+desc(
+ `
+Tests that using a destroyed texture in writeTexture fails.
+- x= {destroyed, not destroyed (control case)}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ })
+ );
+
+ if (destroyed) {
+ texture.destroy();
+ }
+
+ t.expectValidationError(
+ () => t.queue.writeTexture({ texture }, new Uint8Array(4), { bytesPerRow: 4 }, [1, 1, 1]),
+ destroyed
+ );
+});
+
+g.test('copyTextureToTexture').
+desc(
+ `
+Tests that using a destroyed texture in copyTextureToTexture fails.
+- x= {not destroyed (control case), src destroyed, dst destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', ['none', 'src', 'dst', 'both'])).
+fn((t) => {
+ const src = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC
+ })
+ );
+ const dst = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture({ texture: src }, { texture: dst }, [1, 1, 1]);
+ const commandBuffer = encoder.finish();
+
+ let shouldError = true;
+ switch (t.params.destroyed) {
+ case 'none':
+ shouldError = false;
+ break;
+ case 'src':
+ src.destroy();
+ break;
+ case 'dst':
+ dst.destroy();
+ break;
+ case 'both':
+ src.destroy();
+ dst.destroy();
+ break;
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, shouldError);
+});
+
+g.test('copyBufferToTexture').
+desc(
+ `
+Tests that using a destroyed texture in copyBufferToTexture fails.
+- x= {not destroyed (control case), dst destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const buffer = t.trackForCleanup(
+ t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_SRC })
+ );
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture({ buffer }, { texture }, [1, 1, 1]);
+ const commandBuffer = encoder.finish();
+
+ if (destroyed) {
+ texture.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('copyTextureToBuffer').
+desc(
+ `
+Tests that using a destroyed texture in copyTextureToBuffer fails.
+- x= {not destroyed (control case), src destroyed}
+ `
+).
+paramsSubcasesOnly((u) => u.combine('destroyed', [false, true])).
+fn((t) => {
+ const { destroyed } = t.params;
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC
+ })
+ );
+ const buffer = t.trackForCleanup(
+ t.device.createBuffer({ size: 4, usage: GPUBufferUsage.COPY_DST })
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToBuffer({ texture }, { buffer }, [1, 1, 1]);
+ const commandBuffer = encoder.finish();
+
+ if (destroyed) {
+ texture.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('setBindGroup').
+desc(
+ `
+Tests that using a destroyed texture referenced by a bindGroup set with setBindGroup fails
+- x= {not destroyed (control case), destroyed}
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('destroyed', [false, true]).
+combine('encoderType', ['compute pass', 'render pass', 'render bundle'])
+).
+fn((t) => {
+ const { destroyed, encoderType } = t.params;
+ const { device } = t;
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ })
+ );
+
+ const layout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ texture: {}
+ }]
+
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource: texture.createView() }]
+ });
+
+ const { encoder, finish } = t.createEncoder(encoderType);
+ encoder.setBindGroup(0, bindGroup);
+ const commandBuffer = finish();
+
+ if (destroyed) {
+ texture.destroy();
+ }
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, destroyed);
+});
+
+g.test('beginRenderPass').
+desc(
+ `
+Tests that using a destroyed texture referenced by a render pass fails
+- x= {not destroyed (control case), colorAttachment destroyed, depthAttachment destroyed, resolveTarget destroyed}
+ `
+).
+paramsSubcasesOnly((u) =>
+u.combine('textureToDestroy', [
+'none',
+'colorAttachment',
+'resolveAttachment',
+'depthStencilAttachment']
+)
+).
+fn((t) => {
+ const { textureToDestroy } = t.params;
+ const { device } = t;
+
+ const colorAttachment = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ sampleCount: 4,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const resolveAttachment = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const depthStencilAttachment = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'depth32float',
+ sampleCount: 4,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ resolveTarget: resolveAttachment.createView(),
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment: {
+ view: depthStencilAttachment.createView(),
+ depthClearValue: 0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store'
+ }
+ });
+ pass.end();
+ const commandBuffer = encoder.finish();
+
+ switch (textureToDestroy) {
+ case 'none':
+ break;
+ case 'colorAttachment':
+ colorAttachment.destroy();
+ break;
+ case 'resolveAttachment':
+ resolveAttachment.destroy();
+ break;
+ case 'depthStencilAttachment':
+ depthStencilAttachment.destroy();
+ break;
+ default:
+ unreachable();
+ }
+
+ const shouldError = textureToDestroy !== 'none';
+
+ t.expectValidationError(() => {
+ t.queue.submit([commandBuffer]);
+ }, shouldError);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/submit.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/submit.spec.js
new file mode 100644
index 0000000000..fc31992d58
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/submit.spec.js
@@ -0,0 +1,47 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests submit validation.
+
+Note: destroyed buffer/texture/querySet are tested in destroyed/. (unless it gets moved here)
+Note: buffer map state is tested in ./buffer_mapped.spec.ts.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('command_buffer,device_mismatch').
+desc(
+ `
+ Tests submit cannot be called with command buffers created from another device
+ Test with two command buffers to make sure all command buffers can be validated:
+ - cb0 and cb1 from same device
+ - cb0 and cb1 from different device
+ `
+).
+paramsSubcasesOnly([
+{ cb0Mismatched: false, cb1Mismatched: false }, // control case
+{ cb0Mismatched: true, cb1Mismatched: false },
+{ cb0Mismatched: false, cb1Mismatched: true }]
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { cb0Mismatched, cb1Mismatched } = t.params;
+ const mismatched = cb0Mismatched || cb1Mismatched;
+
+ const encoder0 = cb0Mismatched ?
+ t.mismatchedDevice.createCommandEncoder() :
+ t.device.createCommandEncoder();
+ const cb0 = encoder0.finish();
+
+ const encoder1 = cb1Mismatched ?
+ t.mismatchedDevice.createCommandEncoder() :
+ t.device.createCommandEncoder();
+ const cb1 = encoder1.finish();
+
+ t.expectValidationError(() => {
+ t.device.queue.submit([cb0, cb1]);
+ }, mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeBuffer.spec.js
new file mode 100644
index 0000000000..f7cff5bb79
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeBuffer.spec.js
@@ -0,0 +1,200 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests writeBuffer validation.
+
+Note: buffer map state is tested in ./buffer_mapped.spec.ts.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ kTypedArrayBufferViewConstructors } from
+
+
+'../../../../common/util/util.js';
+import { Float16Array } from '../../../../external/petamoriken/float16/float16.js';
+import { GPUConst } from '../../../constants.js';
+import { kResourceStates } from '../../../gpu_test.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('buffer_state').
+desc(
+ `
+ Test that the buffer used for GPUQueue.writeBuffer() must be valid. Tests calling writeBuffer
+ with {valid, invalid, destroyed} buffer.
+ `
+).
+params((u) => u.combine('bufferState', kResourceStates)).
+fn((t) => {
+ const { bufferState } = t.params;
+ const buffer = t.createBufferWithState(bufferState, {
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ const data = new Uint8Array(16);
+ const _valid = bufferState === 'valid';
+
+ t.expectValidationError(() => {
+ t.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
+ }, !_valid);
+});
+
+g.test('ranges').
+desc(
+ `
+ Tests that the data ranges given to GPUQueue.writeBuffer() are properly validated. Tests calling
+ writeBuffer with both TypedArrays and ArrayBuffers and checks that the data offset and size is
+ interpreted correctly for both.
+ - When passing a TypedArray the data offset and size is given in elements.
+ - When passing an ArrayBuffer the data offset and size is given in bytes.
+
+ Also verifies that the specified data range:
+ - Describes a valid range of the destination buffer and source buffer.
+ - Fits fully within the destination buffer.
+ - Has a byte size which is a multiple of 4.
+ `
+).
+fn((t) => {
+ const queue = t.device.queue;
+
+ function runTest(arrayType, testBuffer) {
+ const elementSize = arrayType.BYTES_PER_ELEMENT;
+ const bufferSize = 16 * elementSize;
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ const arraySm = testBuffer ?
+ new arrayType(8).buffer :
+ new arrayType(8);
+ const arrayMd = testBuffer ?
+ new arrayType(16).buffer :
+ new arrayType(16);
+ const arrayLg = testBuffer ?
+ new arrayType(32).buffer :
+ new arrayType(32);
+
+ if (elementSize < 4) {
+ const array15 = testBuffer ?
+ new arrayType(15).buffer :
+ new arrayType(15);
+
+ // Writing the full buffer that isn't 4-byte aligned.
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, array15));
+
+ // Writing from an offset that causes source to be 4-byte aligned.
+ queue.writeBuffer(buffer, 0, array15, 3);
+
+ // Writing from an offset that causes the source to not be 4-byte aligned.
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arrayMd, 3));
+
+ // Writing with a size that is not 4-byte aligned.
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 0, 7));
+ }
+
+ // Writing the full buffer without offsets.
+ queue.writeBuffer(buffer, 0, arraySm);
+ queue.writeBuffer(buffer, 0, arrayMd);
+ t.expectValidationError(() => queue.writeBuffer(buffer, 0, arrayLg));
+
+ // Writing the full buffer with a 4-byte aligned offset.
+ queue.writeBuffer(buffer, 8, arraySm);
+ t.expectValidationError(() => queue.writeBuffer(buffer, 8, arrayMd));
+
+ // Writing the full buffer with a unaligned offset.
+ t.expectValidationError(() => queue.writeBuffer(buffer, 3, arraySm));
+
+ // Writing remainder of buffer from offset.
+ queue.writeBuffer(buffer, 0, arraySm, 4);
+ queue.writeBuffer(buffer, 0, arrayMd, 4);
+ t.expectValidationError(() => queue.writeBuffer(buffer, 0, arrayLg, 4));
+
+ // Writing a larger buffer from an offset that allows it to fit in the destination.
+ queue.writeBuffer(buffer, 0, arrayLg, 16);
+
+ // Writing with both an offset and size.
+ queue.writeBuffer(buffer, 0, arraySm, 4, 4);
+
+ // Writing with a size that extends past the source buffer length.
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 0, 16));
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 4, 8));
+
+ // Writing with a size that is 4-byte aligned but an offset that is not.
+ queue.writeBuffer(buffer, 0, arraySm, 3, 4);
+
+ // Writing zero bytes at the end of the buffer.
+ queue.writeBuffer(buffer, bufferSize, arraySm, 0, 0);
+
+ // Writing with a buffer offset that is out of range of buffer size.
+ t.expectValidationError(() => queue.writeBuffer(buffer, bufferSize + 4, arraySm, 0, 0));
+
+ // Writing zero bytes from the end of the data.
+ queue.writeBuffer(buffer, 0, arraySm, 8, 0);
+
+ // Writing with a data offset that is out of range of data size.
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 9, 0));
+
+ // Writing with a data offset that is out of range of data size with implicit copy size.
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, 9, undefined));
+
+ // A data offset of undefined should be treated as 0.
+ queue.writeBuffer(buffer, 0, arraySm, undefined, 8);
+ t.shouldThrow('OperationError', () => queue.writeBuffer(buffer, 0, arraySm, undefined, 12));
+ }
+
+ runTest(Uint8Array, true);
+
+ for (const arrayType of kTypedArrayBufferViewConstructors) {
+ if (arrayType === Float16Array) {
+ // Skip Float16Array since it is supplied by an external module, so there isn't an overload for it.
+ continue;
+ }
+ runTest(arrayType, false);
+ }
+});
+
+g.test('usages').
+desc(
+ `
+ Tests calling writeBuffer with the buffer missed COPY_DST usage.
+ - buffer {with, without} COPY DST usage
+ `
+).
+paramsSubcasesOnly([
+{ usage: GPUConst.BufferUsage.COPY_DST, _valid: true }, // control case
+{ usage: GPUConst.BufferUsage.STORAGE, _valid: false }, // without COPY_DST usage
+{ usage: GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_SRC, _valid: false }, // with other usage
+{ usage: GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_DST, _valid: true } // with COPY_DST usage
+]).
+fn((t) => {
+ const { usage, _valid } = t.params;
+ const buffer = t.device.createBuffer({ size: 16, usage });
+ const data = new Uint8Array(16);
+
+ t.expectValidationError(() => {
+ t.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
+ }, !_valid);
+});
+
+g.test('buffer,device_mismatch').
+desc('Tests writeBuffer cannot be called with a buffer created from another device.').
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const buffer = sourceDevice.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(buffer);
+
+ const data = new Uint8Array(16);
+
+ t.expectValidationError(() => {
+ t.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
+ }, mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeTexture.spec.js
new file mode 100644
index 0000000000..31727c7945
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/writeTexture.spec.js
@@ -0,0 +1,110 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Tests writeTexture validation.`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUConst } from '../../../constants.js';
+import { kResourceStates } from '../../../gpu_test.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('texture_state').
+desc(
+ `
+ Test that the texture used for GPUQueue.writeTexture() must be valid. Tests calling writeTexture
+ with {valid, invalid, destroyed} texture.
+ `
+).
+params((u) => u.combine('textureState', kResourceStates)).
+fn((t) => {
+ const { textureState } = t.params;
+ const texture = t.createTextureWithState(textureState);
+ const data = new Uint8Array(16);
+ const size = [1, 1];
+
+ const isValid = textureState === 'valid';
+
+ t.expectValidationError(() => {
+ t.device.queue.writeTexture({ texture }, data, {}, size);
+ }, !isValid);
+});
+
+g.test('usages').
+desc(
+ `
+ Tests calling writeTexture with the texture missed COPY_DST usage.
+ - texture {with, without} COPY DST usage
+ `
+).
+paramsSubcasesOnly([
+{ usage: GPUConst.TextureUsage.COPY_DST }, // control case
+{ usage: GPUConst.TextureUsage.STORAGE_BINDING },
+{ usage: GPUConst.TextureUsage.STORAGE_BINDING | GPUConst.TextureUsage.COPY_SRC },
+{ usage: GPUConst.TextureUsage.STORAGE_BINDING | GPUConst.TextureUsage.COPY_DST }]
+).
+fn((t) => {
+ const { usage } = t.params;
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16 },
+ usage,
+ format: 'rgba8unorm'
+ });
+ const data = new Uint8Array(16);
+ const size = [1, 1];
+
+ const isValid = usage & GPUConst.TextureUsage.COPY_DST ? true : false;
+ t.expectValidationError(() => {
+ t.device.queue.writeTexture({ texture }, data, {}, size);
+ }, !isValid);
+});
+
+g.test('sample_count').
+desc(
+ `
+ Test that the texture sample count. Check that a validation error is generated if sample count is
+ not 1.
+ `
+).
+params((u) => u.combine('sampleCount', [1, 4])).
+fn((t) => {
+ const { sampleCount } = t.params;
+ const texture = t.device.createTexture({
+ size: { width: 16, height: 16 },
+ sampleCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const data = new Uint8Array(16);
+ const size = [1, 1];
+
+ const isValid = sampleCount === 1;
+
+ t.expectValidationError(() => {
+ t.device.queue.writeTexture({ texture }, data, {}, size);
+ }, !isValid);
+});
+
+g.test('texture,device_mismatch').
+desc('Tests writeTexture cannot be called with a texture created from another device.').
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const texture = sourceDevice.createTexture({
+ size: { width: 16, height: 16 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ t.trackForCleanup(texture);
+
+ const data = new Uint8Array(16);
+ const size = [1, 1];
+
+ t.expectValidationError(() => {
+ t.device.queue.writeTexture({ texture }, data, {}, size);
+ }, mismatched);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/attachment_compatibility.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/attachment_compatibility.spec.js
new file mode 100644
index 0000000000..7712bf64c4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/attachment_compatibility.spec.js
@@ -0,0 +1,690 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation for attachment compatibility between render passes, bundles, and pipelines
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import { kMaxColorAttachmentsToTest, kTextureSampleCounts } from '../../../capability_info.js';
+import {
+ kRegularTextureFormats,
+ kSizedDepthStencilFormats,
+ kUnsizedDepthStencilFormats,
+ kTextureFormatInfo,
+ filterFormatsByFeature,
+ getFeaturesForFormats } from
+'../../../format_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+const kColorAttachmentCounts = range(kMaxColorAttachmentsToTest, (i) => i + 1);
+const kColorAttachments = kColorAttachmentCounts.
+map((count) => {
+ // generate cases with 0..1 null attachments at different location
+ // e.g. count == 2
+ // [
+ // [1, 1],
+ // [0, 1],
+ // [1, 0],
+ // ]
+ // 0 (false) means null attachment, 1 (true) means non-null attachment, at the slot
+
+ // Special cases: we need at least a color attachment, when we don't have depth stencil attachment
+ if (count === 1) {
+ return [[1]];
+ }
+ if (count === 2) {
+ return [
+ [1, 1],
+ [0, 1],
+ [1, 0]];
+
+ }
+
+ // [1, 1, ..., 1]: all color attachment are used
+ let result = [new Array(count).fill(true)];
+
+ // [1, 0, 1, ..., 1]: generate cases with one null attachment at different locations
+ result = result.concat(
+ range(count, (i) => {
+ const r = new Array(count).fill(true);
+ r[i] = false;
+ return r;
+ })
+ );
+
+ // [1, 0, 1, ..., 0, 1]: generate cases with two null attachments at different locations
+ // To reduce test run time, limit the attachment count to <= 4
+ if (count <= 4) {
+ result = result.concat(
+ range(count - 1, (i) => {
+ const cases = [];
+ for (let j = i + 1; j < count; j++) {
+ const r = new Array(count).fill(true);
+ r[i] = false;
+ r[j] = false;
+ cases.push(r);
+ }
+ return cases;
+ }).flat()
+ );
+ }
+
+ return result;
+}).
+flat();
+
+const kDepthStencilAttachmentFormats = [
+undefined,
+...kSizedDepthStencilFormats,
+...kUnsizedDepthStencilFormats];
+
+
+const kFeaturesForDepthStencilAttachmentFormats = getFeaturesForFormats([
+...kSizedDepthStencilFormats,
+...kUnsizedDepthStencilFormats]
+);
+
+class F extends ValidationTest {
+ createAttachmentTextureView(format, sampleCount) {
+ return this.device.
+ createTexture({
+ // Size matching the "arbitrary" size used by ValidationTest helpers.
+ size: [16, 16, 1],
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount
+ }).
+ createView();
+ }
+
+ createColorAttachment(
+ format,
+ sampleCount)
+ {
+ return format === null ?
+ null :
+ {
+ view: this.createAttachmentTextureView(format, sampleCount),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ };
+ }
+
+ createDepthAttachment(
+ format,
+ sampleCount)
+ {
+ const attachment = {
+ view: this.createAttachmentTextureView(format, sampleCount)
+ };
+ if (kTextureFormatInfo[format].depth) {
+ attachment.depthClearValue = 0;
+ attachment.depthLoadOp = 'clear';
+ attachment.depthStoreOp = 'discard';
+ }
+ if (kTextureFormatInfo[format].stencil) {
+ attachment.stencilClearValue = 1;
+ attachment.stencilLoadOp = 'clear';
+ attachment.stencilStoreOp = 'discard';
+ }
+ return attachment;
+ }
+
+ createRenderPipeline(
+ targets,
+ depthStencil,
+ sampleCount,
+ cullMode)
+ {
+ return this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+ }`
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: '@fragment fn main() {}'
+ }),
+ entryPoint: 'main',
+ targets
+ },
+ primitive: { topology: 'triangle-list', cullMode },
+ depthStencil,
+ multisample: { count: sampleCount }
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kColorAttachmentFormats = kRegularTextureFormats.filter(
+ (format) => !!kTextureFormatInfo[format].colorRender
+);
+
+g.test('render_pass_and_bundle,color_format').
+desc('Test that color attachment formats in render passes and bundles must match.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('passFormat', kColorAttachmentFormats).
+combine('bundleFormat', kColorAttachmentFormats)
+).
+fn((t) => {
+ const { passFormat, bundleFormat } = t.params;
+
+ t.skipIfTextureFormatNotSupported(passFormat, bundleFormat);
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: [bundleFormat]
+ });
+ const bundle = bundleEncoder.finish();
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('non-pass');
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [t.createColorAttachment(passFormat)]
+ });
+ pass.executeBundles([bundle]);
+ pass.end();
+ validateFinishAndSubmit(passFormat === bundleFormat, true);
+});
+
+g.test('render_pass_and_bundle,color_count').
+desc(
+ `
+ Test that the number of color attachments in render passes and bundles must match.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('passCount', kColorAttachmentCounts).
+combine('bundleCount', kColorAttachmentCounts)
+).
+fn((t) => {
+ const { passCount, bundleCount } = t.params;
+
+ const { maxColorAttachments } = t.device.limits;
+ t.skipIf(
+ passCount > maxColorAttachments,
+ `passCount: ${passCount} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+ t.skipIf(
+ bundleCount > maxColorAttachments,
+ `bundleCount: ${bundleCount} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: range(bundleCount, () => 'rgba8uint')
+ });
+ const bundle = bundleEncoder.finish();
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('non-pass');
+ const pass = encoder.beginRenderPass({
+ colorAttachments: range(passCount, () => t.createColorAttachment('rgba8uint'))
+ });
+ pass.executeBundles([bundle]);
+ pass.end();
+ validateFinishAndSubmit(passCount === bundleCount, true);
+});
+
+g.test('render_pass_and_bundle,color_sparse').
+desc(
+ `
+ Test that each of color attachments in render passes and bundles must match.
+ `
+).
+params((u) =>
+u //
+// introduce attachmentCount to make it easier to split the test
+.combine('attachmentCount', kColorAttachmentCounts).
+beginSubcases().
+combine('passAttachments', kColorAttachments).
+combine('bundleAttachments', kColorAttachments).
+filter(
+ (p) =>
+ p.attachmentCount === p.passAttachments.length &&
+ p.attachmentCount === p.bundleAttachments.length
+)
+).
+fn((t) => {
+ const { passAttachments, bundleAttachments } = t.params;
+
+ const { maxColorAttachments } = t.device.limits;
+ t.skipIf(
+ passAttachments.length > maxColorAttachments,
+ `num passAttachments: ${passAttachments.length} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+ t.skipIf(
+ bundleAttachments.length > maxColorAttachments,
+ `num bundleAttachments: ${bundleAttachments.length} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+
+ const colorFormats = bundleAttachments.map((i) => i ? 'rgba8uint' : null);
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats
+ });
+ const bundle = bundleEncoder.finish();
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('non-pass');
+ const colorAttachments = passAttachments.map((i) =>
+ t.createColorAttachment(i ? 'rgba8uint' : null)
+ );
+ const pass = encoder.beginRenderPass({
+ colorAttachments
+ });
+ pass.executeBundles([bundle]);
+ pass.end();
+ validateFinishAndSubmit(
+ passAttachments.every((v, i) => v === bundleAttachments[i]),
+ true
+ );
+});
+
+g.test('render_pass_and_bundle,depth_format').
+desc('Test that the depth attachment format in render passes and bundles must match.').
+params((u) =>
+u //
+.combine('passFeature', kFeaturesForDepthStencilAttachmentFormats).
+combine('bundleFeature', kFeaturesForDepthStencilAttachmentFormats).
+beginSubcases().
+expand('passFormat', ({ passFeature }) =>
+filterFormatsByFeature(passFeature, kDepthStencilAttachmentFormats)
+).
+expand('bundleFormat', ({ bundleFeature }) =>
+filterFormatsByFeature(bundleFeature, kDepthStencilAttachmentFormats)
+)
+).
+beforeAllSubcases((t) => {
+ const { passFeature, bundleFeature } = t.params;
+ t.selectDeviceOrSkipTestCase([passFeature, bundleFeature]);
+}).
+fn((t) => {
+ const { passFormat, bundleFormat } = t.params;
+
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm'],
+ depthStencilFormat: bundleFormat
+ });
+ const bundle = bundleEncoder.finish();
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('non-pass');
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [t.createColorAttachment('rgba8unorm')],
+ depthStencilAttachment:
+ passFormat !== undefined ? t.createDepthAttachment(passFormat) : undefined
+ });
+ pass.executeBundles([bundle]);
+ pass.end();
+ validateFinishAndSubmit(passFormat === bundleFormat, true);
+});
+
+g.test('render_pass_and_bundle,sample_count').
+desc('Test that the sample count in render passes and bundles must match.').
+paramsSubcasesOnly((u) =>
+u //
+.combine('renderSampleCount', kTextureSampleCounts).
+combine('bundleSampleCount', kTextureSampleCounts)
+).
+fn((t) => {
+ const { renderSampleCount, bundleSampleCount } = t.params;
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm'],
+ sampleCount: bundleSampleCount
+ });
+ const bundle = bundleEncoder.finish();
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('non-pass');
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [t.createColorAttachment('rgba8unorm', renderSampleCount)]
+ });
+ pass.executeBundles([bundle]);
+ pass.end();
+ validateFinishAndSubmit(renderSampleCount === bundleSampleCount, true);
+});
+
+g.test('render_pass_and_bundle,device_mismatch').
+desc('Test that render passes cannot be called with bundles created from another device.').
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const format = 'r16float';
+ const bundleEncoder = sourceDevice.createRenderBundleEncoder({
+ colorFormats: [format]
+ });
+ const bundle = bundleEncoder.finish();
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder('non-pass');
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [t.createColorAttachment(format)]
+ });
+ pass.executeBundles([bundle]);
+ pass.end();
+ validateFinishAndSubmit(!mismatched, true);
+});
+
+g.test('render_pass_or_bundle_and_pipeline,color_format').
+desc(
+ `
+Test that color attachment formats in render passes or bundles match the pipeline color format.
+`
+).
+params((u) =>
+u.
+combine('encoderType', ['render pass', 'render bundle']).
+beginSubcases().
+combine('encoderFormat', kColorAttachmentFormats).
+combine('pipelineFormat', kColorAttachmentFormats)
+).
+fn((t) => {
+ const { encoderType, encoderFormat, pipelineFormat } = t.params;
+
+ t.skipIfTextureFormatNotSupported(encoderFormat, pipelineFormat);
+
+ const pipeline = t.createRenderPipeline([{ format: pipelineFormat, writeMask: 0 }]);
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType, {
+ attachmentInfo: { colorFormats: [encoderFormat] }
+ });
+ encoder.setPipeline(pipeline);
+ validateFinishAndSubmit(encoderFormat === pipelineFormat, true);
+});
+
+g.test('render_pass_or_bundle_and_pipeline,color_count').
+desc(
+ `
+Test that the number of color attachments in render passes or bundles match the pipeline color
+count.
+`
+).
+params((u) =>
+u.
+combine('encoderType', ['render pass', 'render bundle']).
+beginSubcases().
+combine('encoderCount', kColorAttachmentCounts).
+combine('pipelineCount', kColorAttachmentCounts)
+).
+fn((t) => {
+ const { encoderType, encoderCount, pipelineCount } = t.params;
+
+ const { maxColorAttachments } = t.device.limits;
+ t.skipIf(
+ pipelineCount > maxColorAttachments,
+ `pipelineCount: ${pipelineCount} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+ t.skipIf(
+ encoderCount > maxColorAttachments,
+ `encoderCount: ${encoderCount} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+
+ const pipeline = t.createRenderPipeline(
+ range(pipelineCount, () => ({ format: 'rgba8uint', writeMask: 0 }))
+ );
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType, {
+ attachmentInfo: { colorFormats: range(encoderCount, () => 'rgba8uint') }
+ });
+ encoder.setPipeline(pipeline);
+ validateFinishAndSubmit(encoderCount === pipelineCount, true);
+});
+
+g.test('render_pass_or_bundle_and_pipeline,color_sparse').
+desc(
+ `
+Test that each of color attachments in render passes or bundles match that of the pipeline.
+`
+).
+params((u) =>
+u.
+combine('encoderType', ['render pass', 'render bundle'])
+// introduce attachmentCount to make it easier to split the test
+.combine('attachmentCount', kColorAttachmentCounts).
+beginSubcases().
+combine('encoderAttachments', kColorAttachments).
+combine('pipelineAttachments', kColorAttachments).
+filter(
+ (p) =>
+ p.attachmentCount === p.encoderAttachments.length &&
+ p.attachmentCount === p.pipelineAttachments.length
+)
+).
+fn((t) => {
+ const { encoderType, encoderAttachments, pipelineAttachments } = t.params;
+ const { maxColorAttachments } = t.device.limits;
+ t.skipIf(
+ encoderAttachments.length > maxColorAttachments,
+ `num encoderAttachments: ${encoderAttachments.length} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+ t.skipIf(
+ pipelineAttachments.length > maxColorAttachments,
+ `num pipelineAttachments: ${pipelineAttachments.length} > maxColorAttachments for device: ${maxColorAttachments}`
+ );
+
+ const colorTargets = pipelineAttachments.map((i) =>
+ i ? { format: 'rgba8uint', writeMask: 0 } : null
+ );
+ const pipeline = t.createRenderPipeline(colorTargets);
+
+ const colorFormats = encoderAttachments.map((i) => i ? 'rgba8uint' : null);
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType, {
+ attachmentInfo: { colorFormats }
+ });
+ encoder.setPipeline(pipeline);
+ validateFinishAndSubmit(
+ encoderAttachments.every((v, i) => v === pipelineAttachments[i]),
+ true
+ );
+});
+
+g.test('render_pass_or_bundle_and_pipeline,depth_format').
+desc(
+ `
+Test that the depth attachment format in render passes or bundles match the pipeline depth format.
+`
+).
+params((u) =>
+u.
+combine('encoderType', ['render pass', 'render bundle']).
+combine('encoderFormatFeature', kFeaturesForDepthStencilAttachmentFormats).
+combine('pipelineFormatFeature', kFeaturesForDepthStencilAttachmentFormats).
+beginSubcases().
+expand('encoderFormat', ({ encoderFormatFeature }) =>
+filterFormatsByFeature(encoderFormatFeature, kDepthStencilAttachmentFormats)
+).
+expand('pipelineFormat', ({ pipelineFormatFeature }) =>
+filterFormatsByFeature(pipelineFormatFeature, kDepthStencilAttachmentFormats)
+)
+).
+beforeAllSubcases((t) => {
+ const { encoderFormatFeature, pipelineFormatFeature } = t.params;
+ t.selectDeviceOrSkipTestCase([encoderFormatFeature, pipelineFormatFeature]);
+}).
+fn((t) => {
+ const { encoderType, encoderFormat, pipelineFormat } = t.params;
+
+ const pipeline = t.createRenderPipeline(
+ [{ format: 'rgba8unorm', writeMask: 0 }],
+ pipelineFormat !== undefined ?
+ { format: pipelineFormat, depthCompare: 'always', depthWriteEnabled: false } :
+ undefined
+ );
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType, {
+ attachmentInfo: { colorFormats: ['rgba8unorm'], depthStencilFormat: encoderFormat }
+ });
+ encoder.setPipeline(pipeline);
+ validateFinishAndSubmit(encoderFormat === pipelineFormat, true);
+});
+
+const kStencilFaceStates = [
+{ failOp: 'keep', depthFailOp: 'keep', passOp: 'keep' },
+{ failOp: 'zero', depthFailOp: 'zero', passOp: 'zero' }];
+
+
+g.test('render_pass_or_bundle_and_pipeline,depth_stencil_read_only_write_state').
+desc(
+ `
+Test that the depth stencil read only state in render passes or bundles is compatible with the depth stencil write state of the pipeline.
+`
+).
+params((u) =>
+u.
+combine('encoderType', ['render pass', 'render bundle']).
+combine('format', kDepthStencilAttachmentFormats).
+beginSubcases()
+// pass/bundle state
+.combine('depthReadOnly', [false, true]).
+combine('stencilReadOnly', [false, true]).
+combine('stencilFront', kStencilFaceStates).
+combine('stencilBack', kStencilFaceStates)
+// pipeline state
+.combine('depthWriteEnabled', [false, true]).
+combine('stencilWriteMask', [0, 0xffffffff]).
+combine('cullMode', ['none', 'front', 'back']).
+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) {
+ return false;
+ }
+ // If the format has no stencil aspect, the stencil state operation must be 'keep'
+ // in order to create a valid render pipeline.
+ if (
+ !depthStencilInfo.stencil && (
+ p.stencilFront.failOp !== 'keep' || p.stencilBack.failOp !== 'keep'))
+ {
+ return false;
+ }
+ }
+ // No depthStencil attachment
+ return true;
+})
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const {
+ encoderType,
+ format,
+ depthReadOnly,
+ stencilReadOnly,
+ depthWriteEnabled,
+ stencilWriteMask,
+ cullMode,
+ stencilFront,
+ stencilBack
+ } = t.params;
+
+ const pipeline = t.createRenderPipeline(
+ [{ format: 'rgba8unorm', writeMask: 0 }],
+ format === undefined ?
+ undefined :
+ {
+ format,
+ depthWriteEnabled,
+ depthCompare: 'always',
+ stencilWriteMask,
+ stencilFront,
+ stencilBack
+ },
+ 1,
+ cullMode
+ );
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType, {
+ attachmentInfo: {
+ colorFormats: ['rgba8unorm'],
+ depthStencilFormat: format,
+ depthReadOnly,
+ stencilReadOnly
+ }
+ });
+ encoder.setPipeline(pipeline);
+
+ let writesDepth = false;
+ let writesStencil = false;
+ if (format) {
+ writesDepth = depthWriteEnabled;
+ if (stencilWriteMask !== 0) {
+ if (
+ cullMode !== 'front' && (
+ stencilFront.passOp !== 'keep' ||
+ stencilFront.depthFailOp !== 'keep' ||
+ stencilFront.failOp !== 'keep'))
+ {
+ writesStencil = true;
+ }
+ if (
+ cullMode !== 'back' && (
+ stencilBack.passOp !== 'keep' ||
+ stencilBack.depthFailOp !== 'keep' ||
+ stencilBack.failOp !== 'keep'))
+ {
+ writesStencil = true;
+ }
+ }
+ }
+
+ let isValid = true;
+ if (writesDepth) {
+ isValid &&= !depthReadOnly;
+ }
+ if (writesStencil) {
+ isValid &&= !stencilReadOnly;
+ }
+
+ validateFinishAndSubmit(isValid, true);
+});
+
+g.test('render_pass_or_bundle_and_pipeline,sample_count').
+desc(
+ `
+Test that the sample count in render passes or bundles match the pipeline sample count for both color texture and depthstencil texture.
+`
+).
+params((u) =>
+u.
+combine('encoderType', ['render pass', 'render bundle']).
+combine('attachmentType', ['color', 'depthstencil']).
+beginSubcases().
+combine('encoderSampleCount', kTextureSampleCounts).
+combine('pipelineSampleCount', kTextureSampleCounts)
+).
+fn((t) => {
+ const { encoderType, attachmentType, encoderSampleCount, pipelineSampleCount } = t.params;
+
+ const colorFormats = attachmentType === 'color' ? ['rgba8unorm'] : [];
+ const depthStencilFormat =
+ attachmentType === 'depthstencil' ? 'depth24plus-stencil8' : undefined;
+
+ const pipeline = t.createRenderPipeline(
+ colorFormats.map((format) => ({ format, writeMask: 0 })),
+ depthStencilFormat ?
+ { format: depthStencilFormat, depthWriteEnabled: false, depthCompare: 'always' } :
+ undefined,
+ pipelineSampleCount
+ );
+
+ const { encoder, validateFinishAndSubmit } = t.createEncoder(encoderType, {
+ attachmentInfo: { colorFormats, depthStencilFormat, sampleCount: encoderSampleCount }
+ });
+ encoder.setPipeline(pipeline);
+ validateFinishAndSubmit(encoderSampleCount === pipelineSampleCount, true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/render_pass_descriptor.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/render_pass_descriptor.spec.js
new file mode 100644
index 0000000000..5108b21fc7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/render_pass_descriptor.spec.js
@@ -0,0 +1,1097 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+render pass descriptor validation tests.
+
+TODO: review for completeness
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import { kMaxColorAttachmentsToTest, kQueryTypes } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import {
+ computeBytesPerSampleFromFormats,
+ kDepthStencilFormats,
+ kRenderableColorTextureFormats,
+ kTextureFormatInfo } from
+'../../../format_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+class F extends ValidationTest {
+ createTexture(
+ options =
+
+
+
+
+
+
+
+ {})
+ {
+ const {
+ format = 'rgba8unorm',
+ width = 16,
+ height = 16,
+ arrayLayerCount = 1,
+ mipLevelCount = 1,
+ sampleCount = 1,
+ usage = GPUTextureUsage.RENDER_ATTACHMENT
+ } = options;
+
+ return this.device.createTexture({
+ size: { width, height, depthOrArrayLayers: arrayLayerCount },
+ format,
+ mipLevelCount,
+ sampleCount,
+ usage
+ });
+ }
+
+ getColorAttachment(
+ texture,
+ textureViewDescriptor)
+ {
+ const view = texture.createView(textureViewDescriptor);
+
+ return {
+ view,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ };
+ }
+
+ getDepthStencilAttachment(
+ texture,
+ textureViewDescriptor)
+ {
+ const view = texture.createView(textureViewDescriptor);
+
+ return {
+ view,
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ stencilClearValue: 0,
+ stencilLoadOp: 'clear',
+ stencilStoreOp: 'store'
+ };
+ }
+
+ tryRenderPass(success, descriptor) {
+ const commandEncoder = this.device.createCommandEncoder();
+ const renderPass = commandEncoder.beginRenderPass(descriptor);
+ renderPass.end();
+
+ this.expectValidationError(() => {
+ commandEncoder.finish();
+ }, !success);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('attachments,one_color_attachment').
+desc(`Test that a render pass works with only one color attachment.`).
+fn((t) => {
+ const colorTexture = t.createTexture({ format: 'rgba8unorm' });
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)]
+ };
+
+ t.tryRenderPass(true, descriptor);
+});
+
+g.test('attachments,one_depth_stencil_attachment').
+desc(`Test that a render pass works with only one depthStencil attachment.`).
+fn((t) => {
+ const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
+ const descriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture)
+ };
+
+ t.tryRenderPass(true, descriptor);
+});
+
+g.test('color_attachments,empty').
+desc(
+ `
+ Test that when colorAttachments has all values be 'undefined' or the sequence is empty, the
+ depthStencilAttachment must not be 'undefined'.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('unclampedColorAttachments', [
+[],
+[undefined],
+[undefined, undefined],
+new Array(8).fill(undefined),
+[{ format: 'rgba8unorm' }]]
+).
+combine('hasDepthStencilAttachment', [false, true])
+).
+fn((t) => {
+ const { unclampedColorAttachments, hasDepthStencilAttachment } = t.params;
+ const colorAttachments = unclampedColorAttachments.slice(
+ 0,
+ t.device.limits.maxColorAttachments
+ );
+
+ let isEmptyColorTargets = true;
+ for (let i = 0; i < colorAttachments.length; i++) {
+ if (colorAttachments[i] !== undefined) {
+ isEmptyColorTargets = false;
+ const colorTexture = t.createTexture();
+ colorAttachments[i] = t.getColorAttachment(colorTexture);
+ }
+ }
+
+ const _success = !isEmptyColorTargets || hasDepthStencilAttachment;
+ t.tryRenderPass(_success, {
+ colorAttachments,
+ depthStencilAttachment: hasDepthStencilAttachment ?
+ t.getDepthStencilAttachment(t.createTexture({ format: 'depth24plus-stencil8' })) :
+ undefined
+ });
+});
+
+g.test('color_attachments,limits,maxColorAttachments').
+desc(
+ `
+ Test that the out of bound of color attachment indexes are handled.
+ - a validation error is generated when color attachments exceed the maximum limit(8).
+ `
+).
+paramsSimple([
+{ colorAttachmentsCountVariant: { mult: 1, add: 0 }, _success: true }, // Control case
+{ colorAttachmentsCountVariant: { mult: 1, add: 1 }, _success: false } // Out of bounds
+]).
+fn((t) => {
+ const { colorAttachmentsCountVariant, _success } = t.params;
+ const colorAttachmentsCount = t.makeLimitVariant(
+ 'maxColorAttachments',
+ colorAttachmentsCountVariant
+ );
+
+ const colorAttachments = [];
+ for (let i = 0; i < colorAttachmentsCount; i++) {
+ const colorTexture = t.createTexture({ format: 'r8unorm' });
+ colorAttachments.push(t.getColorAttachment(colorTexture));
+ }
+
+ t.tryRenderPass(_success, { colorAttachments });
+});
+
+g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,aligned').
+desc(
+ `
+ Test that the total bytes per sample of the formats of the color attachments must be no greater
+ than maxColorAttachmentBytesPerSample when the components are aligned (same format).
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+beginSubcases().
+combine(
+ 'attachmentCount',
+ range(kMaxColorAttachmentsToTest, (i) => i + 1)
+)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn((t) => {
+ const { format, attachmentCount } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ t.skipIf(
+ attachmentCount > t.device.limits.maxColorAttachments,
+ `attachmentCount: ${attachmentCount} > maxColorAttachments: ${t.device.limits.maxColorAttachments}`
+ );
+
+ const colorAttachments = [];
+ for (let i = 0; i < attachmentCount; i++) {
+ const colorTexture = t.createTexture({ format });
+ colorAttachments.push(t.getColorAttachment(colorTexture));
+ }
+ const shouldError =
+ info.colorRender === undefined ||
+ computeBytesPerSampleFromFormats(range(attachmentCount, () => format)) >
+ t.device.limits.maxColorAttachmentBytesPerSample;
+
+ t.tryRenderPass(!shouldError, { colorAttachments });
+});
+
+g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned').
+desc(
+ `
+ Test that the total bytes per sample of the formats of the color attachments must be no greater
+ than maxColorAttachmentBytesPerSample when the components are (potentially) unaligned.
+ `
+).
+params((u) =>
+u.combineWithParams([
+// Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though
+// 1+4+8+16+1 < 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm
+// 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']
+
+},
+{
+ formats: [
+ 'r32float',
+ 'rgba8unorm',
+ 'rgba32float',
+ 'r8unorm',
+ 'r8unorm']
+
+}]
+)
+).
+fn((t) => {
+ const { formats } = t.params;
+
+ t.skipIf(
+ formats.length > t.device.limits.maxColorAttachments,
+ `numColorAttachments: ${formats.length} > maxColorAttachments: ${t.device.limits.maxColorAttachments}`
+ );
+
+ const colorAttachments = [];
+ for (const format of formats) {
+ const colorTexture = t.createTexture({ format });
+ colorAttachments.push(t.getColorAttachment(colorTexture));
+ }
+
+ const success =
+ computeBytesPerSampleFromFormats(formats) <= t.device.limits.maxColorAttachmentBytesPerSample;
+
+ t.tryRenderPass(success, { colorAttachments });
+});
+
+g.test('attachments,same_size').
+desc(
+ `
+ Test that attachments have the same size. Otherwise, a validation error should be generated.
+ - Succeed if all attachments have the same size.
+ - Fail if one of the color attachments has a different size.
+ - Fail if the depth stencil attachment has a different size.
+ `
+).
+fn((t) => {
+ const colorTexture1x1A = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
+ const colorTexture1x1B = t.createTexture({ width: 1, height: 1, format: 'rgba8unorm' });
+ const colorTexture2x2 = t.createTexture({ width: 2, height: 2, format: 'rgba8unorm' });
+ const depthStencilTexture1x1 = t.createTexture({
+ width: 1,
+ height: 1,
+ format: 'depth24plus-stencil8'
+ });
+ const depthStencilTexture2x2 = t.createTexture({
+ width: 2,
+ height: 2,
+ format: 'depth24plus-stencil8'
+ });
+
+ {
+ // Control case: all the same size (1x1)
+ const descriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture1x1A),
+ t.getColorAttachment(colorTexture1x1B)],
+
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture1x1)
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+ {
+ // One of the color attachments has a different size
+ const descriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture1x1A),
+ t.getColorAttachment(colorTexture2x2)]
+
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ // The depth stencil attachment has a different size
+ const descriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture1x1A),
+ t.getColorAttachment(colorTexture1x1B)],
+
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture2x2)
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+});
+
+g.test('attachments,color_depth_mismatch').
+desc(`Test that attachments match whether they are used for color or depth stencil.`).
+fn((t) => {
+ const colorTexture = t.createTexture({ format: 'rgba8unorm' });
+ const depthStencilTexture = t.createTexture({ format: 'depth24plus-stencil8' });
+
+ {
+ // Using depth-stencil for color
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(depthStencilTexture)]
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ // Using color for depth-stencil
+ const descriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(colorTexture)
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+});
+
+g.test('attachments,layer_count').
+desc(
+ `
+ Test the layer counts for color or depth stencil.
+ - Fail if using 2D array texture view with arrayLayerCount > 1.
+ - Succeed if using 2D array texture view that covers the first layer of the texture.
+ - Succeed if using 2D array texture view that covers the last layer for depth stencil.
+ `
+).
+paramsSimple([
+{ arrayLayerCount: 5, baseArrayLayer: 0, _success: false },
+{ arrayLayerCount: 1, baseArrayLayer: 0, _success: true },
+{ arrayLayerCount: 1, baseArrayLayer: 9, _success: true }]
+).
+fn((t) => {
+ const { arrayLayerCount, baseArrayLayer, _success } = t.params;
+
+ const ARRAY_LAYER_COUNT = 10;
+ const MIP_LEVEL_COUNT = 1;
+ const COLOR_FORMAT = 'rgba8unorm';
+ const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
+
+ const colorTexture = t.createTexture({
+ format: COLOR_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT
+ });
+ const depthStencilTexture = t.createTexture({
+ format: DEPTH_STENCIL_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT
+ });
+
+ const baseTextureViewDescriptor = {
+ dimension: '2d-array',
+ baseArrayLayer,
+ arrayLayerCount,
+ baseMipLevel: 0,
+ mipLevelCount: MIP_LEVEL_COUNT
+ };
+
+ {
+ // Check 2D array texture view for color
+ const textureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: COLOR_FORMAT
+ };
+
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)]
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+ {
+ // Check 2D array texture view for depth stencil
+ const textureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: DEPTH_STENCIL_FORMAT
+ };
+
+ const descriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(
+ depthStencilTexture,
+ textureViewDescriptor
+ )
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+});
+
+g.test('attachments,mip_level_count').
+desc(
+ `
+ Test the mip level count for color or depth stencil.
+ - Fail if using 2D texture view with mipLevelCount > 1.
+ - Succeed if using 2D texture view that covers the first level of the texture.
+ - Succeed if using 2D texture view that covers the last level of the texture.
+ `
+).
+paramsSimple([
+{ mipLevelCount: 2, baseMipLevel: 0, _success: false },
+{ mipLevelCount: 1, baseMipLevel: 0, _success: true },
+{ mipLevelCount: 1, baseMipLevel: 3, _success: true }]
+).
+fn((t) => {
+ const { mipLevelCount, baseMipLevel, _success } = t.params;
+
+ const ARRAY_LAYER_COUNT = 1;
+ const MIP_LEVEL_COUNT = 4;
+ const COLOR_FORMAT = 'rgba8unorm';
+ const DEPTH_STENCIL_FORMAT = 'depth24plus-stencil8';
+
+ const colorTexture = t.createTexture({
+ format: COLOR_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT
+ });
+ const depthStencilTexture = t.createTexture({
+ format: DEPTH_STENCIL_FORMAT,
+ width: 32,
+ height: 32,
+ mipLevelCount: MIP_LEVEL_COUNT,
+ arrayLayerCount: ARRAY_LAYER_COUNT
+ });
+
+ const baseTextureViewDescriptor = {
+ dimension: '2d',
+ baseArrayLayer: 0,
+ arrayLayerCount: ARRAY_LAYER_COUNT,
+ baseMipLevel,
+ mipLevelCount
+ };
+
+ {
+ // Check 2D texture view for color
+ const textureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: COLOR_FORMAT
+ };
+
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture, textureViewDescriptor)]
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+ {
+ // Check 2D texture view for depth stencil
+ const textureViewDescriptor = {
+ ...baseTextureViewDescriptor,
+ format: DEPTH_STENCIL_FORMAT
+ };
+
+ const descriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(
+ depthStencilTexture,
+ textureViewDescriptor
+ )
+ };
+
+ t.tryRenderPass(_success, descriptor);
+ }
+});
+
+g.test('color_attachments,non_multisampled').
+desc(
+ `
+ Test that setting a resolve target is invalid if the color attachments is non multisampled.
+ `
+).
+fn((t) => {
+ const colorTexture = t.createTexture({ sampleCount: 1 });
+ const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
+
+ const descriptor = {
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ resolveTarget: resolveTargetTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ t.tryRenderPass(false, descriptor);
+});
+
+g.test('color_attachments,sample_count').
+desc(
+ `
+ Test the usages of multisampled textures for color attachments.
+ - Succeed if using a multisampled color attachment without setting a resolve target.
+ - Fail if using multiple color attachments with different sample counts.
+ `
+).
+fn((t) => {
+ const colorTexture = t.createTexture({ sampleCount: 1 });
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+
+ {
+ // It is allowed to use a multisampled color attachment without setting resolve target
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(multisampledColorTexture)]
+ };
+ t.tryRenderPass(true, descriptor);
+ }
+ {
+ // It is not allowed to use multiple color attachments with different sample counts
+ const descriptor = {
+ colorAttachments: [
+ t.getColorAttachment(colorTexture),
+ t.getColorAttachment(multisampledColorTexture)]
+
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+});
+
+g.test('resolveTarget,sample_count').
+desc(
+ `
+ Test that using multisampled resolve target is invalid for color attachments.
+ `
+).
+fn((t) => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const multisampledResolveTargetTexture = t.createTexture({ sampleCount: 4 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = multisampledResolveTargetTexture.createView();
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(false, descriptor);
+});
+
+g.test('resolveTarget,array_layer_count').
+desc(
+ `
+ Test that using a resolve target with array layer count is greater than 1 is invalid for color
+ attachments.
+ `
+).
+fn((t) => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ arrayLayerCount: 2 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView({ dimension: '2d-array' });
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(false, descriptor);
+});
+
+g.test('resolveTarget,mipmap_level_count').
+desc(
+ `
+ Test that using a resolve target with that mipmap level count is greater than 1 is invalid for
+ color attachments.
+ `
+).
+fn((t) => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ mipLevelCount: 2 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(false, descriptor);
+});
+
+g.test('resolveTarget,usage').
+desc(
+ `
+ Test that using a resolve target whose usage is not RENDER_ATTACHMENT is invalid for color
+ attachments.
+ `
+).
+paramsSimple([
+{ usage: GPUConst.TextureUsage.COPY_SRC | GPUConst.TextureUsage.COPY_DST },
+{ usage: GPUConst.TextureUsage.STORAGE_BINDING | GPUConst.TextureUsage.TEXTURE_BINDING },
+{ usage: GPUConst.TextureUsage.STORAGE_BINDING | GPUConst.TextureUsage.STORAGE },
+{ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.TEXTURE_BINDING }]
+).
+fn((t) => {
+ const { usage } = t.params;
+
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ usage });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ const isValid = usage & GPUConst.TextureUsage.RENDER_ATTACHMENT ? true : false;
+ t.tryRenderPass(isValid, descriptor);
+});
+
+g.test('resolveTarget,error_state').
+desc(`Test that a resolve target that has a error is invalid for color attachments.`).
+fn((t) => {
+ const ARRAY_LAYER_COUNT = 1;
+
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ arrayLayerCount: ARRAY_LAYER_COUNT });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ t.expectValidationError(() => {
+ colorAttachment.resolveTarget = resolveTargetTexture.createView({
+ dimension: '2d',
+ format: 'rgba8unorm',
+ baseArrayLayer: ARRAY_LAYER_COUNT + 1
+ });
+ });
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(false, descriptor);
+});
+
+g.test('resolveTarget,single_sample_count').
+desc(
+ `
+ Test that a resolve target that has multi sample color attachment and a single resolve target is
+ valid.
+ `
+).
+fn((t) => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ sampleCount: 1 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(true, descriptor);
+});
+
+g.test('resolveTarget,different_format').
+desc(`Test that a resolve target that has a different format is invalid.`).
+fn((t) => {
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({ format: 'bgra8unorm' });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTexture.createView();
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(false, descriptor);
+});
+
+g.test('resolveTarget,different_size').
+desc(
+ `
+ Test that a resolve target that has a different size with the color attachment is invalid.
+ `
+).
+fn((t) => {
+ const size = 16;
+ const multisampledColorTexture = t.createTexture({ width: size, height: size, sampleCount: 4 });
+ const resolveTargetTexture = t.createTexture({
+ width: size * 2,
+ height: size * 2,
+ mipLevelCount: 2
+ });
+
+ {
+ const resolveTargetTextureView = resolveTargetTexture.createView({
+ baseMipLevel: 0,
+ mipLevelCount: 1
+ });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTextureView;
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ const resolveTargetTextureView = resolveTargetTexture.createView({ baseMipLevel: 1 });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTargetTextureView;
+
+ const descriptor = {
+ colorAttachments: [colorAttachment]
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+});
+
+g.test('depth_stencil_attachment,sample_counts_mismatch').
+desc(
+ `
+ Test that the depth stencil attachment that has different number of samples with the color
+ attachment is invalid.
+ `
+).
+fn((t) => {
+ const multisampledDepthStencilTexture = t.createTexture({
+ sampleCount: 4,
+ format: 'depth24plus-stencil8'
+ });
+
+ {
+ // It is not allowed to use a depth stencil attachment whose sample count is different from
+ // the one of the color attachment.
+ const depthStencilTexture = t.createTexture({
+ sampleCount: 1,
+ format: 'depth24plus-stencil8'
+ });
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
+ depthStencilAttachment: t.getDepthStencilAttachment(depthStencilTexture)
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ const colorTexture = t.createTexture({ sampleCount: 1 });
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture)
+ };
+
+ t.tryRenderPass(false, descriptor);
+ }
+ {
+ // It is allowed to use a multisampled depth stencil attachment whose sample count is equal to
+ // the one of the color attachment.
+ const multisampledColorTexture = t.createTexture({ sampleCount: 4 });
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(multisampledColorTexture)],
+ depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture)
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+ {
+ // It is allowed to use a multisampled depth stencil attachment with no color attachment.
+ const descriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: t.getDepthStencilAttachment(multisampledDepthStencilTexture)
+ };
+
+ t.tryRenderPass(true, descriptor);
+ }
+});
+
+g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadOnly').
+desc(
+ `
+ Test GPURenderPassDepthStencilAttachment Usage:
+ - if the format has a depth aspect:
+ - if depthReadOnly is true
+ - depthLoadOp and depthStoreOp must not be provided
+ - else:
+ - depthLoadOp and depthStoreOp must be provided
+ - if the format has a stencil aspect:
+ - if stencilReadOnly is true
+ - stencilLoadOp and stencilStoreOp must not be provided
+ - else:
+ - stencilLoadOp and stencilStoreOp must be provided
+ `
+).
+params((u) =>
+u.
+combine('format', kDepthStencilFormats).
+beginSubcases() // Note: It's easier to debug if you comment this line out as you can then run an individual case.
+.combine('depthReadOnly', [undefined, true, false]).
+combine('depthLoadOp', [undefined, 'clear', 'load']).
+combine('depthStoreOp', [undefined, 'discard', 'store']).
+combine('stencilReadOnly', [undefined, true, false]).
+combine('stencilLoadOp', [undefined, 'clear', 'load']).
+combine('stencilStoreOp', [undefined, 'discard', 'store'])
+).
+beforeAllSubcases((t) => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ format,
+ depthReadOnly,
+ depthLoadOp,
+ depthStoreOp,
+ stencilReadOnly,
+ stencilLoadOp,
+ stencilStoreOp
+ } = t.params;
+
+ const depthAttachment = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ })
+ );
+ const depthAttachmentView = depthAttachment.createView();
+
+ const encoder = t.device.createCommandEncoder();
+
+ // If depthLoadOp is "clear", depthClearValue must be provided and must be between 0.0 and 1.0,
+ // and it will be ignored if depthLoadOp is not "clear".
+ const depthClearValue = depthLoadOp === 'clear' ? 0 : undefined;
+ const renderPassDescriptor = {
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: depthAttachmentView,
+ depthLoadOp,
+ depthStoreOp,
+ depthReadOnly,
+ stencilLoadOp,
+ stencilStoreOp,
+ stencilReadOnly,
+ depthClearValue
+ }
+ };
+ const pass = encoder.beginRenderPass(renderPassDescriptor);
+ pass.end();
+
+ const info = kTextureFormatInfo[format];
+ const hasDepthSettings = !!depthLoadOp && !!depthStoreOp && !depthReadOnly;
+ const hasStencilSettings = !!stencilLoadOp && !!stencilStoreOp && !stencilReadOnly;
+ const hasDepth = info.depth;
+ const hasStencil = info.stencil;
+
+ const goodAspectCombo =
+ (hasDepth && hasStencil ? !depthReadOnly === !stencilReadOnly : true) && (
+ hasDepthSettings ? hasDepth : true) && (
+ hasStencilSettings ? hasStencil : true);
+
+ const hasBothDepthOps = !!depthLoadOp && !!depthStoreOp;
+ const hasBothStencilOps = !!stencilLoadOp && !!stencilStoreOp;
+ const hasNeitherDepthOps = !depthLoadOp && !depthStoreOp;
+ const hasNeitherStencilOps = !stencilLoadOp && !stencilStoreOp;
+
+ const goodDepthCombo = hasDepth && !depthReadOnly ? hasBothDepthOps : hasNeitherDepthOps;
+ const goodStencilCombo =
+ hasStencil && !stencilReadOnly ? hasBothStencilOps : hasNeitherStencilOps;
+
+ const shouldError = !goodAspectCombo || !goodDepthCombo || !goodStencilCombo;
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, shouldError);
+});
+
+g.test('depth_stencil_attachment,depth_clear_value').
+desc(
+ `
+ Test that depthClearValue is invalid if the value is out of the range(0.0 and 1.0) only when
+ depthLoadOp is 'clear'.
+ `
+).
+params((u) =>
+u.
+combine('depthLoadOp', ['load', 'clear', undefined]).
+combine('depthClearValue', [undefined, -1.0, 0.0, 0.5, 1.0, 1.5])
+).
+fn((t) => {
+ const { depthLoadOp, depthClearValue } = t.params;
+
+ const depthStencilTexture = t.createTexture({
+ format: depthLoadOp === undefined ? 'stencil8' : 'depth24plus-stencil8'
+ });
+ const depthStencilAttachment = t.getDepthStencilAttachment(depthStencilTexture);
+ depthStencilAttachment.depthClearValue = depthClearValue;
+ depthStencilAttachment.depthLoadOp = depthLoadOp;
+ if (depthLoadOp === undefined) {
+ depthStencilAttachment.depthStoreOp = undefined;
+ }
+
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(t.createTexture())],
+ depthStencilAttachment
+ };
+
+ // We can not check for out of range because NaN is not out of range.
+ // So (v < 0.0 || v > 1.0) would return false when depthClearValue is undefined (NaN)
+ const isDepthValueInRange = depthClearValue >= 0.0 && depthClearValue <= 1.0;
+ const isInvalid = depthLoadOp === 'clear' && !isDepthValueInRange;
+
+ t.tryRenderPass(!isInvalid, descriptor);
+});
+
+g.test('resolveTarget,format_supports_resolve').
+desc(
+ `
+ For all formats that support 'multisample', test that they can be used as a resolveTarget
+ if and only if they support 'resolve'.
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+filter((t) => kTextureFormatInfo[t.format].multisample)
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const multisampledColorTexture = t.createTexture({ format, sampleCount: 4 });
+ const resolveTarget = t.createTexture({ format });
+
+ const colorAttachment = t.getColorAttachment(multisampledColorTexture);
+ colorAttachment.resolveTarget = resolveTarget.createView();
+
+ t.tryRenderPass(!!info.colorRender?.resolve, {
+ colorAttachments: [colorAttachment]
+ });
+});
+
+g.test('timestampWrites,query_set_type').
+desc(
+ `
+ Test that all entries of the timestampWrites must have type 'timestamp'. If all query types are
+ not 'timestamp', a validation error should be generated.
+ `
+).
+params((u) =>
+u //
+.combine('queryType', kQueryTypes)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+}).
+fn((t) => {
+ const { queryType } = t.params;
+
+ const timestampWrites = {
+ querySet: t.device.createQuerySet({ type: queryType, count: 2 }),
+ beginningOfPassWriteIndex: 0,
+ endOfPassWriteIndex: 1
+ };
+
+ const isValid = queryType === 'timestamp';
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ timestampWrites
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+});
+
+g.test('timestampWrite,query_index').
+desc(
+ `Test that querySet.count should be greater than timestampWrite.queryIndex, and that the
+ query indexes are unique.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('beginningOfPassWriteIndex', [undefined, 0, 1, 2, 3]).
+combine('endOfPassWriteIndex', [undefined, 0, 1, 2, 3])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+}).
+fn((t) => {
+ const { beginningOfPassWriteIndex, endOfPassWriteIndex } = t.params;
+
+ const querySetCount = 2;
+
+ const timestampWrites = {
+ querySet: t.device.createQuerySet({ type: 'timestamp', count: querySetCount }),
+ beginningOfPassWriteIndex,
+ endOfPassWriteIndex
+ };
+
+ const isValid =
+ beginningOfPassWriteIndex !== endOfPassWriteIndex && (
+ beginningOfPassWriteIndex === undefined || beginningOfPassWriteIndex < querySetCount) && (
+ endOfPassWriteIndex === undefined || endOfPassWriteIndex < querySetCount);
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ timestampWrites
+ };
+
+ t.tryRenderPass(isValid, descriptor);
+});
+
+g.test('occlusionQuerySet,query_set_type').
+desc(`Test that occlusionQuerySet must have type 'occlusion'.`).
+params((u) => u.combine('queryType', kQueryTypes)).
+beforeAllSubcases((t) => {
+ if (t.params.queryType === 'timestamp') {
+ t.selectDeviceOrSkipTestCase(['timestamp-query']);
+ }
+}).
+fn((t) => {
+ const { queryType } = t.params;
+
+ const querySet = t.device.createQuerySet({
+ type: queryType,
+ count: 1
+ });
+
+ const colorTexture = t.createTexture();
+ const descriptor = {
+ colorAttachments: [t.getColorAttachment(colorTexture)],
+ occlusionQuerySet: querySet
+ };
+
+ const isValid = queryType === 'occlusion';
+ t.tryRenderPass(isValid, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/resolve.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/resolve.spec.js
new file mode 100644
index 0000000000..b9d60091f8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pass/resolve.spec.js
@@ -0,0 +1,192 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for render pass resolve.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUConst } from '../../../constants.js';
+import { ValidationTest } from '../validation_test.js';
+
+const kNumColorAttachments = 4;
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('resolve_attachment').
+desc(
+ `
+Test various validation behaviors when a resolveTarget is provided.
+
+- base case (valid).
+- resolve source is not multisampled.
+- resolve target is not single sampled.
+- resolve target missing RENDER_ATTACHMENT usage.
+- resolve target must have exactly one subresource:
+ - base mip level {0, >0}, mip level count {1, >1}.
+ - base array layer {0, >0}, array layer count {1, >1}.
+- resolve target GPUTextureView is invalid
+- resolve source and target have different formats.
+ - rgba8unorm -> {bgra8unorm, rgba8unorm-srgb}
+ - {bgra8unorm, rgba8unorm-srgb} -> rgba8unorm
+ - test with other color attachments having a different format
+- resolve source and target have different sizes.
+`
+).
+paramsSimple([
+// control case should be valid
+{ _valid: true },
+// a single sampled resolve source should cause a validation error.
+{ colorAttachmentSamples: 1, _valid: false },
+// a multisampled resolve target should cause a validation error.
+{ resolveTargetSamples: 4, _valid: false },
+// resolveTargetUsage without RENDER_ATTACHMENT usage should cause a validation error.
+{ resolveTargetUsage: GPUConst.TextureUsage.COPY_SRC, _valid: false },
+// non-zero resolve target base mip level should be valid.
+{
+ resolveTargetViewBaseMipLevel: 1,
+ resolveTargetHeight: 4,
+ resolveTargetWidth: 4,
+ _valid: true
+},
+// a validation error should be created when resolveTarget is invalid.
+{ resolveTargetInvalid: true, _valid: false },
+// a validation error should be created when mip count > 1
+{ resolveTargetViewMipCount: 2, _valid: false },
+{
+ resolveTargetViewBaseMipLevel: 1,
+ resolveTargetViewMipCount: 2,
+ resolveTargetHeight: 4,
+ resolveTargetWidth: 4,
+ _valid: false
+},
+// non-zero resolve target base array layer should be valid.
+{ resolveTargetViewBaseArrayLayer: 1, _valid: true },
+// a validation error should be created when array layer count > 1
+{ resolveTargetViewArrayLayerCount: 2, _valid: false },
+{ resolveTargetViewBaseArrayLayer: 1, resolveTargetViewArrayLayerCount: 2, _valid: false },
+// other color attachments resolving with a different format should be valid.
+{ otherAttachmentFormat: 'bgra8unorm', _valid: true },
+// mismatched colorAttachment and resolveTarget formats should cause a validation error.
+{ colorAttachmentFormat: 'bgra8unorm', _valid: false },
+{ colorAttachmentFormat: 'rgba8unorm-srgb', _valid: false },
+{ resolveTargetFormat: 'bgra8unorm', _valid: false },
+{ resolveTargetFormat: 'rgba8unorm-srgb', _valid: false },
+// mismatched colorAttachment and resolveTarget sizes should cause a validation error.
+{ colorAttachmentHeight: 4, _valid: false },
+{ colorAttachmentWidth: 4, _valid: false },
+{ resolveTargetHeight: 4, _valid: false },
+{ resolveTargetWidth: 4, _valid: false }]
+).
+fn((t) => {
+ const {
+ colorAttachmentFormat = 'rgba8unorm',
+ resolveTargetFormat = 'rgba8unorm',
+ otherAttachmentFormat = 'rgba8unorm',
+ colorAttachmentSamples = 4,
+ resolveTargetSamples = 1,
+ resolveTargetUsage = GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ resolveTargetInvalid = false,
+ resolveTargetViewMipCount = 1,
+ resolveTargetViewBaseMipLevel = 0,
+ resolveTargetViewArrayLayerCount = 1,
+ resolveTargetViewBaseArrayLayer = 0,
+ colorAttachmentHeight = 2,
+ colorAttachmentWidth = 2,
+ resolveTargetHeight = 2,
+ resolveTargetWidth = 2,
+ _valid
+ } = t.params;
+
+ // Run the test in a nested loop such that the configured color attachment with resolve target
+ // is tested while occupying each individual colorAttachment slot.
+ for (let resolveSlot = 0; resolveSlot < kNumColorAttachments; resolveSlot++) {
+ const renderPassColorAttachmentDescriptors = [];
+ for (
+ let colorAttachmentSlot = 0;
+ colorAttachmentSlot < kNumColorAttachments;
+ colorAttachmentSlot++)
+ {
+ // resolveSlot === colorAttachmentSlot denotes the color attachment slot that contains the
+ // color attachment with resolve target.
+ if (resolveSlot === colorAttachmentSlot) {
+ // Create the color attachment with resolve target with the configurable parameters.
+ const resolveSourceColorAttachment = t.device.createTexture({
+ format: colorAttachmentFormat,
+ size: {
+ width: colorAttachmentWidth,
+ height: colorAttachmentHeight,
+ depthOrArrayLayers: 1
+ },
+ sampleCount: colorAttachmentSamples,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const resolveTarget = t.device.createTexture({
+ format: resolveTargetFormat,
+ size: {
+ width: resolveTargetWidth,
+ height: resolveTargetHeight,
+ depthOrArrayLayers:
+ resolveTargetViewBaseArrayLayer + resolveTargetViewArrayLayerCount
+ },
+ sampleCount: resolveTargetSamples,
+ mipLevelCount: resolveTargetViewBaseMipLevel + resolveTargetViewMipCount,
+ usage: resolveTargetUsage
+ });
+
+ renderPassColorAttachmentDescriptors.push({
+ view: resolveSourceColorAttachment.createView(),
+ loadOp: 'load',
+ storeOp: 'discard',
+ resolveTarget: resolveTargetInvalid ?
+ t.getErrorTextureView() :
+ resolveTarget.createView({
+ dimension: resolveTargetViewArrayLayerCount === 1 ? '2d' : '2d-array',
+ mipLevelCount: resolveTargetViewMipCount,
+ arrayLayerCount: resolveTargetViewArrayLayerCount,
+ baseMipLevel: resolveTargetViewBaseMipLevel,
+ baseArrayLayer: resolveTargetViewBaseArrayLayer
+ })
+ });
+ } else {
+ // Create a basic texture to fill other color attachment slots. This texture's dimensions
+ // and sample count must match the resolve source color attachment to be valid.
+ const colorAttachment = t.device.createTexture({
+ format: otherAttachmentFormat,
+ size: {
+ width: colorAttachmentWidth,
+ height: colorAttachmentHeight,
+ depthOrArrayLayers: 1
+ },
+ sampleCount: colorAttachmentSamples,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const resolveTarget = t.device.createTexture({
+ format: otherAttachmentFormat,
+ size: {
+ width: colorAttachmentWidth,
+ height: colorAttachmentHeight,
+ depthOrArrayLayers: 1
+ },
+ sampleCount: 1,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ renderPassColorAttachmentDescriptors.push({
+ view: colorAttachment.createView(),
+ loadOp: 'load',
+ storeOp: 'discard',
+ resolveTarget: resolveTarget.createView()
+ });
+ }
+ }
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: renderPassColorAttachmentDescriptors
+ });
+ pass.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !_valid);
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/common.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/common.js
new file mode 100644
index 0000000000..2b4283b32c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/common.js
@@ -0,0 +1,68 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kTextureFormatInfo } from '../../../format_info.js';import { getFragmentShaderCodeWithOutput,
+getPlainTypeInfo,
+kDefaultVertexShaderCode } from
+'../../../util/shader.js';
+import { ValidationTest } from '../validation_test.js';
+
+const values = [0, 1, 0, 1];
+export class CreateRenderPipelineValidationTest extends ValidationTest {
+ getDescriptor(
+ options =
+
+
+
+
+
+
+
+ {})
+ {
+ const defaultTargets = [{ format: 'rgba8unorm' }];
+ const {
+ primitive = {},
+ targets = defaultTargets,
+ multisample = {},
+ depthStencil,
+ fragmentShaderCode = getFragmentShaderCodeWithOutput([
+ {
+ values,
+ plainType: getPlainTypeInfo(
+ kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].sampleType
+ ),
+ componentCount: 4
+ }]
+ ),
+ noFragment = false,
+ fragmentConstants = {}
+ } = options;
+
+ return {
+ vertex: {
+ module: this.device.createShaderModule({
+ code: kDefaultVertexShaderCode
+ }),
+ entryPoint: 'main'
+ },
+ fragment: noFragment ?
+ undefined :
+ {
+ module: this.device.createShaderModule({
+ code: fragmentShaderCode
+ }),
+ entryPoint: 'main',
+ targets,
+ constants: fragmentConstants
+ },
+ layout: this.getPipelineLayout(),
+ primitive,
+ multisample,
+ depthStencil
+ };
+ }
+
+ getPipelineLayout() {
+ return this.device.createPipelineLayout({ bindGroupLayouts: [] });
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.js
new file mode 100644
index 0000000000..d47041b4fc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.js
@@ -0,0 +1,304 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests validation of GPUDepthStencilState of createRenderPipeline.
+`;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 { getFragmentShaderCodeWithOutput } from '../../../util/shader.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+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)).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = t.getDescriptor({
+ depthStencil: { format, depthWriteEnabled: false, depthCompare: 'always' }
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, !!info.depth || !!info.stencil, descriptor);
+});
+
+g.test('depthCompare_optional').
+desc(
+ `The depthCompare in depthStencilState is optional for stencil-only formats but
+ required for formats with a depth if depthCompare is not used for anything.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kDepthStencilFormats).
+beginSubcases().
+combine('depthCompare', ['always', undefined]).
+combine('depthWriteEnabled', [false, true, undefined]).
+combine('stencilFrontDepthFailOp', ['keep', 'zero']).
+combine('stencilBackDepthFailOp', ['keep', 'zero'])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const {
+ isAsync,
+ format,
+ depthCompare,
+ depthWriteEnabled,
+ stencilFrontDepthFailOp,
+ stencilBackDepthFailOp
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+ const descriptor = t.getDescriptor({
+ depthStencil: {
+ format,
+ depthCompare,
+ depthWriteEnabled,
+ stencilFront: { depthFailOp: stencilFrontDepthFailOp },
+ stencilBack: { depthFailOp: stencilBackDepthFailOp }
+ }
+ });
+
+ const depthFailOpsAreKeep =
+ stencilFrontDepthFailOp === 'keep' && stencilBackDepthFailOp === 'keep';
+ const stencilStateIsDefault = depthFailOpsAreKeep;
+ let success = true;
+ if (depthWriteEnabled || depthCompare && depthCompare !== 'always') {
+ if (!info.depth) success = false;
+ }
+ if (!stencilStateIsDefault) {
+ if (!info.stencil) success = false;
+ }
+ if (info.depth) {
+ if (depthWriteEnabled === undefined) success = false;
+ if (depthWriteEnabled || !depthFailOpsAreKeep) {
+ if (depthCompare === undefined) success = false;
+ }
+ }
+
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+});
+
+g.test('depthWriteEnabled_optional').
+desc(
+ `The depthWriteEnabled in depthStencilState is optional for stencil-only formats but required for formats with a depth.`
+).
+params((u) => u.combine('isAsync', [false, true]).combine('format', kDepthStencilFormats)).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format } = t.params;
+ const info = kTextureFormatInfo[format];
+ const descriptor = t.getDescriptor({
+ depthStencil: { format, depthCompare: 'always', depthWriteEnabled: undefined }
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, !info.depth, descriptor);
+});
+
+g.test('depth_test').
+desc(
+ `Depth aspect must be contained in the format if depth test is enabled in depthStencilState.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kDepthStencilFormats).
+combine('depthCompare', kCompareFunctions)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format, depthCompare } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = t.getDescriptor({
+ depthStencil: { format, depthCompare, depthWriteEnabled: false }
+ });
+
+ const depthTestEnabled = depthCompare !== undefined && depthCompare !== 'always';
+ t.doCreateRenderPipelineTest(isAsync, !depthTestEnabled || !!info.depth, descriptor);
+});
+
+g.test('depth_write').
+desc(
+ `Depth aspect must be contained in the format if depth write is enabled in depthStencilState.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kDepthStencilFormats).
+combine('depthWriteEnabled', [false, true])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format, depthWriteEnabled } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = t.getDescriptor({
+ depthStencil: { format, depthWriteEnabled, depthCompare: 'always' }
+ });
+ t.doCreateRenderPipelineTest(isAsync, !depthWriteEnabled || !!info.depth, descriptor);
+});
+
+g.test('depth_write,frag_depth').
+desc(`Depth aspect must be contained in the format if frag_depth is written in fragment stage.`).
+params((u) =>
+u.combine('isAsync', [false, true]).combine('format', [undefined, ...kDepthStencilFormats])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ if (format !== undefined) {
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ }
+}).
+fn((t) => {
+ const { isAsync, format } = t.params;
+
+ const descriptor = t.getDescriptor({
+ // Keep one color target so that the pipeline is still valid with no depth stencil target.
+ targets: [{ format: 'rgba8unorm' }],
+ depthStencil: format ?
+ { format, depthWriteEnabled: true, depthCompare: 'always' } :
+ undefined,
+ fragmentShaderCode: getFragmentShaderCodeWithOutput(
+ [{ values: [1, 1, 1, 1], plainType: 'f32', componentCount: 4 }],
+ { value: 0.5 }
+ )
+ });
+
+ const hasDepth = format ? !!kTextureFormatInfo[format].depth : false;
+ t.doCreateRenderPipelineTest(isAsync, hasDepth, descriptor);
+});
+
+g.test('stencil_test').
+desc(
+ `Stencil aspect must be contained in the format if stencil test is enabled in depthStencilState.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kDepthStencilFormats).
+combine('face', ['front', 'back']).
+combine('compare', [undefined, ...kCompareFunctions])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format, face, compare } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ let descriptor;
+ if (face === 'front') {
+ descriptor = t.getDescriptor({
+ depthStencil: {
+ format,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilFront: { compare }
+ }
+ });
+ } else {
+ descriptor = t.getDescriptor({
+ depthStencil: {
+ format,
+ depthWriteEnabled: false,
+ depthCompare: 'always',
+ stencilBack: { compare }
+ }
+ });
+ }
+
+ const stencilTestEnabled = compare !== undefined && compare !== 'always';
+ t.doCreateRenderPipelineTest(isAsync, !stencilTestEnabled || !!info.stencil, descriptor);
+});
+
+g.test('stencil_write').
+desc(
+ `Stencil aspect must be contained in the format if stencil write is enabled in depthStencilState.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kDepthStencilFormats).
+combine('faceAndOpType', [
+'frontFailOp',
+'frontDepthFailOp',
+'frontPassOp',
+'backFailOp',
+'backDepthFailOp',
+'backPassOp']
+).
+combine('op', [undefined, ...kStencilOperations])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format, faceAndOpType, op } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const common = {
+ format,
+ depthWriteEnabled: false,
+ depthCompare: 'always'
+ };
+ let depthStencil;
+ switch (faceAndOpType) {
+ case 'frontFailOp':
+ depthStencil = { ...common, stencilFront: { failOp: op } };
+ break;
+ case 'frontDepthFailOp':
+ depthStencil = { ...common, stencilFront: { depthFailOp: op } };
+ break;
+ case 'frontPassOp':
+ depthStencil = { ...common, stencilFront: { passOp: op } };
+ break;
+ case 'backFailOp':
+ depthStencil = { ...common, stencilBack: { failOp: op } };
+ break;
+ case 'backDepthFailOp':
+ depthStencil = { ...common, stencilBack: { depthFailOp: op } };
+ break;
+ case 'backPassOp':
+ depthStencil = { ...common, stencilBack: { passOp: op } };
+ break;
+ default:
+ unreachable();
+ }
+ const descriptor = t.getDescriptor({ depthStencil });
+
+ const stencilWriteEnabled = op !== undefined && op !== 'keep';
+ t.doCreateRenderPipelineTest(isAsync, !stencilWriteEnabled || !!info.stencil, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/fragment_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/fragment_state.spec.js
new file mode 100644
index 0000000000..6e7a46e943
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/fragment_state.spec.js
@@ -0,0 +1,427 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests validation of GPUFragmentState of createRenderPipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import {
+ kBlendFactors,
+ kBlendOperations,
+ kMaxColorAttachmentsToTest } from
+'../../../capability_info.js';
+import {
+ kTextureFormats,
+ kRenderableColorTextureFormats,
+ kTextureFormatInfo,
+ computeBytesPerSampleFromFormats } from
+'../../../format_info.js';
+import {
+ getFragmentShaderCodeWithOutput,
+ getPlainTypeInfo,
+ kDefaultFragmentShaderCode } from
+'../../../util/shader.js';
+import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+const values = [0, 1, 0, 1];
+
+g.test('color_target_exists').
+desc(`Tests creating a complete render pipeline requires at least one color target state.`).
+params((u) => u.combine('isAsync', [false, true])).
+fn((t) => {
+ const { isAsync } = t.params;
+
+ const goodDescriptor = t.getDescriptor({
+ targets: [{ format: 'rgba8unorm' }]
+ });
+
+ // Control case
+ t.doCreateRenderPipelineTest(isAsync, true, goodDescriptor);
+
+ // Fail because lack of color states
+ const badDescriptor = t.getDescriptor({
+ targets: []
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, false, badDescriptor);
+});
+
+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)).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = t.getDescriptor({ targets: [{ format }] });
+
+ t.doCreateRenderPipelineTest(isAsync, !!info.colorRender, descriptor);
+});
+
+g.test('limits,maxColorAttachments').
+desc(
+ `Tests that color state targets length must not be larger than device.limits.maxColorAttachments.`
+).
+params((u) =>
+u.combine('isAsync', [false, true]).combine('targetsLengthVariant', [
+{ mult: 1, add: 0 },
+{ mult: 1, add: 1 }]
+)
+).
+fn((t) => {
+ const { isAsync, targetsLengthVariant } = t.params;
+ const targetsLength = t.makeLimitVariant('maxColorAttachments', targetsLengthVariant);
+
+ const descriptor = t.getDescriptor({
+ targets: range(targetsLength, (_i) => {
+ return { format: 'rg8unorm', writeMask: 0 };
+ }),
+ fragmentShaderCode: kDefaultFragmentShaderCode,
+ // add a depth stencil so that we can set writeMask to 0 for all color attachments
+ depthStencil: {
+ format: 'depth24plus',
+ depthWriteEnabled: true,
+ depthCompare: 'always'
+ }
+ });
+
+ t.doCreateRenderPipelineTest(
+ isAsync,
+ targetsLength <= t.device.limits.maxColorAttachments,
+ descriptor
+ );
+});
+
+g.test('limits,maxColorAttachmentBytesPerSample,aligned').
+desc(
+ `
+ Tests that the total color attachment bytes per sample must not be larger than
+ maxColorAttachmentBytesPerSample when using the same format for multiple attachments.
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+beginSubcases().
+combine(
+ 'attachmentCount',
+ range(kMaxColorAttachmentsToTest, (i) => i + 1)
+).
+combine('isAsync', [false, true])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn((t) => {
+ const { format, attachmentCount, isAsync } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ t.skipIf(
+ attachmentCount > t.device.limits.maxColorAttachments,
+ `attachmentCount: ${attachmentCount} > maxColorAttachments: ${t.device.limits.maxColorAttachments}`
+ );
+
+ const descriptor = t.getDescriptor({
+ targets: range(attachmentCount, () => {
+ return { format, writeMask: 0 };
+ })
+ });
+ const shouldError =
+ info.colorRender === undefined ||
+ info.colorRender.byteCost * attachmentCount >
+ t.device.limits.maxColorAttachmentBytesPerSample;
+
+ t.doCreateRenderPipelineTest(isAsync, !shouldError, descriptor);
+});
+
+g.test('limits,maxColorAttachmentBytesPerSample,unaligned').
+desc(
+ `
+ Tests that the total color attachment bytes per sample must not be larger than
+ maxColorAttachmentBytesPerSample when using various sets of (potentially) unaligned formats.
+ `
+).
+params((u) =>
+u.
+combineWithParams([
+// Alignment causes the first 1 byte R8Unorm to become 4 bytes. So even though
+// 1+4+8+16+1 < 32, the 4 byte alignment requirement of R32Float makes the first R8Unorm
+// 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']
+
+},
+{
+ formats: [
+ 'r32float',
+ 'rgba8unorm',
+ 'rgba32float',
+ 'r8unorm',
+ 'r8unorm']
+
+}]
+).
+beginSubcases().
+combine('isAsync', [false, true])
+).
+fn((t) => {
+ const { formats, isAsync } = t.params;
+
+ t.skipIf(
+ formats.length > t.device.limits.maxColorAttachments,
+ `numColorAttachments: ${formats.length} > maxColorAttachments: ${t.device.limits.maxColorAttachments}`
+ );
+
+ const success =
+ computeBytesPerSampleFromFormats(formats) <= t.device.limits.maxColorAttachmentBytesPerSample;
+
+ const descriptor = t.getDescriptor({
+ targets: formats.map((f) => {
+ return { format: f, writeMask: 0 };
+ })
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+});
+
+g.test('targets_format_filterable').
+desc(
+ `
+ Tests that color target state format must be filterable if blend is not undefined.
+
+ TODO: info.colorRender.blend now directly says whether the format is blendable. Use that.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', kRenderableColorTextureFormats).
+beginSubcases().
+combine('hasBlend', [false, true])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const { isAsync, format, hasBlend } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = t.getDescriptor({
+ targets: [
+ {
+ format,
+ blend: hasBlend ? { color: {}, alpha: {} } : undefined
+ }]
+
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, !hasBlend || info.color.type === 'float', descriptor);
+});
+
+g.test('targets_blend').
+desc(
+ `
+ For the blend components on either GPUBlendState.color or GPUBlendState.alpha:
+ - Tests if the combination of 'srcFactor', 'dstFactor' and 'operation' is valid (if the blend
+ operation is "min" or "max", srcFactor and dstFactor must be "one").
+ `
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('component', ['color', 'alpha']).
+beginSubcases().
+combine('srcFactor', kBlendFactors).
+combine('dstFactor', kBlendFactors).
+combine('operation', kBlendOperations)
+).
+fn((t) => {
+ const { isAsync, component, srcFactor, dstFactor, operation } = t.params;
+
+ const defaultBlendComponent = {
+ srcFactor: 'src-alpha',
+ dstFactor: 'dst-alpha',
+ operation: 'add'
+ };
+ const blendComponentToTest = {
+ srcFactor,
+ dstFactor,
+ operation
+ };
+ const format = 'rgba8unorm';
+
+ const descriptor = t.getDescriptor({
+ targets: [
+ {
+ format,
+ blend: {
+ color: component === 'color' ? blendComponentToTest : defaultBlendComponent,
+ alpha: component === 'alpha' ? blendComponentToTest : defaultBlendComponent
+ }
+ }]
+
+ });
+
+ if (operation === 'min' || operation === 'max') {
+ const _success = srcFactor === 'one' && dstFactor === 'one';
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+ } else {
+ t.doCreateRenderPipelineTest(isAsync, true, descriptor);
+ }
+});
+
+g.test('targets_write_mask').
+desc(`Tests that color target state write mask must be < 16.`).
+params((u) => u.combine('isAsync', [false, true]).combine('writeMask', [0, 0xf, 0x10, 0x80000001])).
+fn((t) => {
+ const { isAsync, writeMask } = t.params;
+
+ const descriptor = t.getDescriptor({
+ targets: [
+ {
+ format: 'rgba8unorm',
+ writeMask
+ }]
+
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, writeMask < 16, descriptor);
+});
+
+g.test('pipeline_output_targets').
+desc(
+ `Pipeline fragment output types must be compatible with target color state format
+ - The scalar type (f32, i32, or u32) must match the sample type of the format.
+ - The componentCount of the fragment output (e.g. f32, vec2, vec3, vec4) must not have fewer
+ channels than that of the color attachment texture formats. Extra components are allowed and are discarded.
+
+ Otherwise, color state write mask must be 0.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', [undefined, ...kRenderableColorTextureFormats]).
+beginSubcases().
+combine('shaderOutput', [
+undefined,
+...u.combine('scalar', ['f32', 'u32', 'i32']).combine('count', [1, 2, 3, 4])]
+)
+// We only care about testing writeMask if there is an attachment but no shader output.
+.expand('writeMask', (p) =>
+p.format !== undefined && p.shaderOutput !== undefined ? [0, 0x1, 0x2, 0x4, 0x8] : [0xf]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { isAsync, format, writeMask, shaderOutput } = t.params;
+
+ const descriptor = t.getDescriptor({
+ targets: format ? [{ format, writeMask }] : [],
+ // To have a dummy depthStencil attachment to avoid having no attachment at all which is invalid
+ depthStencil: { format: 'depth24plus', depthWriteEnabled: false, depthCompare: 'always' },
+ fragmentShaderCode: getFragmentShaderCodeWithOutput(
+ shaderOutput ?
+ [{ values, plainType: shaderOutput.scalar, componentCount: shaderOutput.count }] :
+ []
+ )
+ });
+
+ let success = true;
+ if (format) {
+ // There is a color target
+ if (shaderOutput) {
+ // The shader outputs to the color target
+ const info = kTextureFormatInfo[format];
+ success =
+ shaderOutput.scalar === getPlainTypeInfo(info.color.type) &&
+ shaderOutput.count >= kTexelRepresentationInfo[format].componentOrder.length;
+ } else {
+ // The shader does not output to the color target
+ success = writeMask === 0;
+ }
+ }
+
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+});
+
+g.test('pipeline_output_targets,blend').
+desc(
+ `On top of requirements from pipeline_output_targets, when blending is enabled and alpha channel is read indicated by any blend factor, an extra requirement is added:
+ - fragment output must be vec4.
+ `
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('format', ['r8unorm', 'rg8unorm', 'rgba8unorm', 'bgra8unorm']).
+combine('componentCount', [1, 2, 3, 4]).
+beginSubcases()
+// The default srcFactor and dstFactor are 'one' and 'zero'. Override just one at a time.
+.combineWithParams([
+...u.combine('colorSrcFactor', kBlendFactors),
+...u.combine('colorDstFactor', kBlendFactors),
+...u.combine('alphaSrcFactor', kBlendFactors),
+...u.combine('alphaDstFactor', kBlendFactors)]
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+}).
+fn((t) => {
+ const sampleType = 'float';
+ const {
+ isAsync,
+ format,
+ componentCount,
+ colorSrcFactor,
+ colorDstFactor,
+ alphaSrcFactor,
+ alphaDstFactor
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const descriptor = t.getDescriptor({
+ targets: [
+ {
+ format,
+ blend: {
+ color: { srcFactor: colorSrcFactor, dstFactor: colorDstFactor },
+ alpha: { srcFactor: alphaSrcFactor, dstFactor: alphaDstFactor }
+ }
+ }],
+
+ fragmentShaderCode: getFragmentShaderCodeWithOutput([
+ { values, plainType: getPlainTypeInfo(sampleType), componentCount }]
+ )
+ });
+
+ const colorBlendReadsSrcAlpha =
+ colorSrcFactor?.includes('src-alpha') || colorDstFactor?.includes('src-alpha');
+ const meetsExtraBlendingRequirement = !colorBlendReadsSrcAlpha || componentCount === 4;
+ const _success =
+ info.color.type === sampleType &&
+ componentCount >= kTexelRepresentationInfo[format].componentOrder.length &&
+ meetsExtraBlendingRequirement;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/inter_stage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/inter_stage.spec.js
new file mode 100644
index 0000000000..019d725d3c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/inter_stage.spec.js
@@ -0,0 +1,324 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Interface matching between vertex and fragment shader validation for createRenderPipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, range } from '../../../../common/util/util.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+function getVarName(i) {
+ return `v${i}`;
+}
+
+class InterStageMatchingValidationTest extends CreateRenderPipelineValidationTest {
+ getVertexStateWithOutputs(outputs) {
+ return {
+ module: this.device.createShaderModule({
+ code: `
+ struct A {
+ ${outputs.map((v, i) => v.replace('__', getVarName(i))).join(',\n')},
+ @builtin(position) pos: vec4<f32>,
+ }
+ @vertex fn main() -> A {
+ var vertexOut: A;
+ vertexOut.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ return vertexOut;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ };
+ }
+
+ getFragmentStateWithInputs(
+ inputs,
+ hasBuiltinPosition = false)
+ {
+ return {
+ targets: [{ format: 'rgba8unorm' }],
+ module: this.device.createShaderModule({
+ code: `
+ struct B {
+ ${inputs.map((v, i) => v.replace('__', getVarName(i))).join(',\n')},
+ ${hasBuiltinPosition ? '@builtin(position) pos: vec4<f32>' : ''}
+ }
+ @fragment fn main(fragmentIn: B) -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 1.0, 1.0, 1.0);
+ }
+ `
+ }),
+ entryPoint: 'main'
+ };
+ }
+
+ getDescriptorWithStates(
+ vertex,
+ fragment)
+ {
+ return {
+ layout: 'auto',
+ vertex,
+ fragment
+ };
+ }
+}
+
+export const g = makeTestGroup(InterStageMatchingValidationTest);
+
+g.test('location,mismatch').
+desc(`Tests that missing declaration at the same location should fail validation.`).
+params((u) =>
+u.combine('isAsync', [false, true]).combineWithParams([
+{ outputs: ['@location(0) __: f32'], inputs: ['@location(0) __: f32'], _success: true },
+{ outputs: ['@location(0) __: f32'], inputs: ['@location(1) __: f32'], _success: false },
+{ outputs: ['@location(1) __: f32'], inputs: ['@location(0) __: f32'], _success: false },
+{
+ outputs: ['@location(0) __: f32', '@location(1) __: f32'],
+ inputs: ['@location(1) __: f32', '@location(0) __: f32'],
+ _success: true
+},
+{
+ outputs: ['@location(1) __: f32', '@location(0) __: f32'],
+ inputs: ['@location(0) __: f32', '@location(1) __: f32'],
+ _success: true
+}]
+)
+).
+fn((t) => {
+ const { isAsync, outputs, inputs, _success } = t.params;
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs(outputs),
+ t.getFragmentStateWithInputs(inputs)
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('location,superset').
+desc(`TODO: implement after spec is settled: https://github.com/gpuweb/gpuweb/issues/2038`).
+unimplemented();
+
+g.test('location,subset').
+desc(`Tests that validation should fail when vertex output is a subset 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']),
+ t.getFragmentStateWithInputs(['@location(0) fin0: f32', '@location(1) fin1: f32'])
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, false, descriptor);
+});
+
+g.test('type').
+desc(
+ `Tests that validation should fail when type of vertex output and fragment input at the same location doesn't match.`
+).
+params((u) =>
+u.combine('isAsync', [false, true]).combineWithParams([
+{ output: 'f32', input: 'f32' },
+{ output: 'i32', input: 'f32' },
+{ output: 'u32', input: 'f32' },
+{ output: 'u32', input: 'i32' },
+{ output: 'i32', input: 'u32' },
+{ output: 'vec2<f32>', input: 'vec2<f32>' },
+{ output: 'vec3<f32>', input: 'vec2<f32>' },
+{ output: 'vec2<f32>', input: 'vec3<f32>' },
+{ output: 'vec2<f32>', input: 'f32' },
+{ output: 'f32', input: 'vec2<f32>' }]
+)
+).
+fn((t) => {
+ const { isAsync, output, input } = t.params;
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs([`@location(0) @interpolate(flat) vout0: ${output}`]),
+ t.getFragmentStateWithInputs([`@location(0) @interpolate(flat) fin0: ${input}`])
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, output === input, descriptor);
+});
+
+g.test('interpolation_type').
+desc(
+ `Tests that validation should fail when interpolation type of vertex output and fragment input at the same location doesn't match.`
+).
+params((u) =>
+u.combine('isAsync', [false, true]).combineWithParams([
+// default is @interpolate(perspective, center)
+{ output: '', input: '' },
+{ output: '', input: '@interpolate(perspective)', _success: true },
+{ output: '', input: '@interpolate(perspective, center)', _success: true },
+{ output: '@interpolate(perspective)', input: '', _success: true },
+{ output: '', input: '@interpolate(linear)' },
+{ output: '@interpolate(perspective)', input: '@interpolate(perspective)' },
+{ 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)' }]
+)
+).
+fn((t) => {
+ const { isAsync, output, input, _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);
+});
+
+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.`
+).
+params((u) =>
+u.combine('isAsync', [false, true]).combineWithParams([
+// default is @interpolate(perspective, center)
+{ output: '@interpolate(perspective)', input: '@interpolate(perspective)' },
+{
+ output: '@interpolate(perspective)',
+ input: '@interpolate(perspective, center)',
+ _success: true
+},
+{ output: '@interpolate(linear, center)', input: '@interpolate(linear)', _success: true },
+{ output: '@interpolate(flat)', input: '@interpolate(flat)' },
+{ output: '@interpolate(perspective)', input: '@interpolate(perspective, sample)' },
+{ output: '@interpolate(perspective, center)', input: '@interpolate(perspective, sample)' },
+{
+ output: '@interpolate(perspective, center)',
+ input: '@interpolate(perspective, centroid)'
+},
+{ output: '@interpolate(perspective, centroid)', input: '@interpolate(perspective)' }]
+)
+).
+fn((t) => {
+ const { isAsync, output, input, _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);
+});
+
+g.test('max_shader_variable_location').
+desc(
+ `Tests that validation should fail when there is location of user-defined output/input variable >= device.limits.maxInterStageShaderVariables`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true])
+// User defined variable location = maxInterStageShaderVariables + locationDelta
+.combine('locationDelta', [0, -1, -2])
+).
+fn((t) => {
+ const { isAsync, locationDelta } = t.params;
+ const maxInterStageShaderVariables = t.device.limits.maxInterStageShaderVariables;
+ const location = maxInterStageShaderVariables + locationDelta;
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs([`@location(${location}) vout0: f32`]),
+ t.getFragmentStateWithInputs([`@location(${location}) fin0: f32`])
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, location < maxInterStageShaderVariables, descriptor);
+});
+
+g.test('max_components_count,output').
+desc(
+ `Tests that validation should fail when scalar components of all user-defined outputs > max vertex shader output components.`
+).
+params((u) =>
+u.combine('isAsync', [false, true]).combineWithParams([
+// Number of user-defined output scalar components in test shader = device.limits.maxInterStageShaderComponents + numScalarDelta.
+{ numScalarDelta: 0, topology: 'triangle-list', _success: true },
+{ numScalarDelta: 1, topology: 'triangle-list', _success: false },
+{ numScalarDelta: 0, topology: 'point-list', _success: false },
+{ numScalarDelta: -1, topology: 'point-list', _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, numScalarDelta, topology, _success } = t.params;
+
+ const numScalarComponents = t.device.limits.maxInterStageShaderComponents + numScalarDelta;
+
+ 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>`);
+
+ if (numTrailingScalars > 0) {
+ const typeString = numTrailingScalars === 1 ? 'f32' : `vec${numTrailingScalars}<f32>`;
+ outputs.push(`@location(${numVec4}) vout${numVec4}: ${typeString}`);
+ inputs.push(`@location(${numVec4}) fin${numVec4}: ${typeString}`);
+ }
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs(outputs),
+ t.getFragmentStateWithInputs(inputs)
+ );
+ descriptor.primitive = { topology };
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('max_components_count,input').
+desc(
+ `Tests that validation should fail when scalar components of all user-defined inputs > max vertex shader output components.`
+).
+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 }]
+)
+).
+fn((t) => {
+ const { isAsync, numScalarDelta, useExtraBuiltinInputs, _success } = t.params;
+
+ const numScalarComponents = t.device.limits.maxInterStageShaderComponents + numScalarDelta;
+
+ 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>`);
+
+ if (numTrailingScalars > 0) {
+ const typeString = numTrailingScalars === 1 ? 'f32' : `vec${numTrailingScalars}<f32>`;
+ outputs.push(`@location(${numVec4}) vout${numVec4}: ${typeString}`);
+ inputs.push(`@location(${numVec4}) fin${numVec4}: ${typeString}`);
+ }
+
+ 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'
+ );
+ }
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs(outputs),
+ t.getFragmentStateWithInputs(inputs, true)
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/misc.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/misc.spec.js
new file mode 100644
index 0000000000..79dddb51e0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/misc.spec.js
@@ -0,0 +1,98 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+misc createRenderPipeline and createRenderPipelineAsync validation tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kDefaultVertexShaderCode, kDefaultFragmentShaderCode } from '../../../util/shader.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+g.test('basic').
+desc(`Test basic usage of createRenderPipeline.`).
+params((u) => u.combine('isAsync', [false, true])).
+fn((t) => {
+ const { isAsync } = t.params;
+ const descriptor = t.getDescriptor();
+
+ t.doCreateRenderPipelineTest(isAsync, true, descriptor);
+});
+
+g.test('vertex_state_only').
+desc(
+ `Tests creating vertex-state-only render pipeline. A vertex-only render pipeline has no fragment
+state (and thus has no color state), and can be created with or without depth stencil state.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+beginSubcases().
+combine('depthStencilFormat', [
+'depth24plus',
+'depth24plus-stencil8',
+'depth32float',
+'']
+).
+combine('hasColor', [false, true])
+).
+fn((t) => {
+ const { isAsync, depthStencilFormat, hasColor } = t.params;
+
+ let depthStencilState;
+ if (depthStencilFormat === '') {
+ depthStencilState = undefined;
+ } else {
+ depthStencilState = {
+ format: depthStencilFormat,
+ depthWriteEnabled: false,
+ depthCompare: 'always'
+ };
+ }
+
+ // Having targets or not should have no effect in result, since it will not appear in the
+ // descriptor in vertex-only render pipeline
+ const descriptor = t.getDescriptor({
+ noFragment: true,
+ depthStencil: depthStencilState,
+ targets: hasColor ? [{ format: 'rgba8unorm' }] : []
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, true, descriptor);
+});
+
+g.test('pipeline_layout,device_mismatch').
+desc(
+ 'Tests createRenderPipeline(Async) cannot be called with a pipeline layout created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('isAsync', [true, false]).combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { isAsync, mismatched } = t.params;
+
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const layout = sourceDevice.createPipelineLayout({ bindGroupLayouts: [] });
+
+ const format = 'rgba8unorm';
+ const descriptor = {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kDefaultVertexShaderCode
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: kDefaultFragmentShaderCode
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ }
+ };
+
+ t.doCreateRenderPipelineTest(isAsync, !mismatched, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/multisample_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/multisample_state.spec.js
new file mode 100644
index 0000000000..a4305c1b54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/multisample_state.spec.js
@@ -0,0 +1,87 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests validation of GPUMultisampleState of createRenderPipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kDefaultFragmentShaderCode } from '../../../util/shader.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+g.test('count').
+desc(`If multisample.count must either be 1 or 4.`).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+beginSubcases().
+combine('count', [0, 1, 2, 3, 4, 8, 16, 1024])
+).
+fn((t) => {
+ const { isAsync, count } = t.params;
+
+ const descriptor = t.getDescriptor({ multisample: { count, alphaToCoverageEnabled: false } });
+
+ const _success = count === 1 || count === 4;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('alpha_to_coverage,count').
+desc(
+ `If multisample.alphaToCoverageEnabled is true, multisample.count must be greater than 1, e.g. it can only be 4.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('alphaToCoverageEnabled', [false, true]).
+beginSubcases().
+combine('count', [1, 4])
+).
+fn((t) => {
+ const { isAsync, alphaToCoverageEnabled, count } = t.params;
+
+ const descriptor = t.getDescriptor({ multisample: { count, alphaToCoverageEnabled } });
+
+ const _success = alphaToCoverageEnabled ? count === 4 : count === 1 || count === 4;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('alpha_to_coverage,sample_mask').
+desc(
+ `If sample_mask builtin is a pipeline output of fragment, multisample.alphaToCoverageEnabled should be false.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('alphaToCoverageEnabled', [false, true]).
+beginSubcases().
+combine('hasSampleMaskOutput', [false, true])
+).
+fn((t) => {
+ const { isAsync, alphaToCoverageEnabled, hasSampleMaskOutput } = t.params;
+
+ if (t.isCompatibility && hasSampleMaskOutput) {
+ t.skip('WGSL sample_mask is not supported in compatibility mode');
+ }
+
+ const descriptor = t.getDescriptor({
+ multisample: { alphaToCoverageEnabled, count: 4 },
+ fragmentShaderCode: hasSampleMaskOutput ?
+ `
+ struct Output {
+ @builtin(sample_mask) mask_out: u32,
+ @location(0) color : vec4<f32>,
+ }
+ @fragment fn main() -> Output {
+ var o: Output;
+ // We need to make sure this sample_mask isn't optimized out even its value equals "no op".
+ o.mask_out = 0xFFFFFFFFu;
+ o.color = vec4<f32>(1.0, 1.0, 1.0, 1.0);
+ return o;
+ }` :
+ kDefaultFragmentShaderCode
+ });
+
+ const _success = !hasSampleMaskOutput || !alphaToCoverageEnabled;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/overrides.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/overrides.spec.js
new file mode 100644
index 0000000000..e930ecaed6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/overrides.spec.js
@@ -0,0 +1,535 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests validation of pipeline overridable constants of createRenderPipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kValue } from '../../../util/constants.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+g.test('identifier,vertex').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for overridable constants identifiers in vertex state.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ vertexConstants: {}, _success: true },
+{ vertexConstants: { x: 1, y: 1 }, _success: true },
+{ vertexConstants: { x: 1, y: 1, 1: 1, 1000: 1 }, _success: true },
+{ vertexConstants: { 'x\0': 1, y: 1 }, _success: false },
+{ vertexConstants: { xxx: 1 }, _success: false },
+{ vertexConstants: { 1: 1 }, _success: true },
+{ vertexConstants: { 2: 1 }, _success: false },
+{ vertexConstants: { z: 1 }, _success: false }, // pipeline constant id is specified for z
+{ vertexConstants: { w: 1 }, _success: false }, // pipeline constant id is specified for w
+{ vertexConstants: { 1: 1, z: 1 }, _success: false }, // pipeline constant id is specified for z
+{ vertexConstants: { 数: 1 }, _success: true }, // test non-ASCII
+{ vertexConstants: { séquençage: 0 }, _success: false } // test unicode normalization
+])
+).
+fn((t) => {
+ const { isAsync, vertexConstants, _success } = t.params;
+
+ t.doCreateRenderPipelineTest(isAsync, _success, {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ override x: f32 = 0.0;
+ override y: f32 = 0.0;
+ override 数: f32 = 0.0;
+ override séquençage: f32 = 0.0;
+ @id(1) override z: f32 = 0.0;
+ @id(1000) override w: f32 = 1.0;
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(x, y, z, w + 数 + séquençage);
+ }`
+ }),
+ entryPoint: 'main',
+ constants: vertexConstants
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+});
+
+g.test('identifier,fragment').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for overridable constants identifiers in fragment state.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ fragmentConstants: {}, _success: true },
+{ fragmentConstants: { r: 1, g: 1 }, _success: true },
+{ fragmentConstants: { r: 1, g: 1, 1: 1, 1000: 1 }, _success: true },
+{ fragmentConstants: { 'r\0': 1 }, _success: false },
+{ fragmentConstants: { xxx: 1 }, _success: false },
+{ fragmentConstants: { 1: 1 }, _success: true },
+{ fragmentConstants: { 2: 1 }, _success: false },
+{ fragmentConstants: { b: 1 }, _success: false }, // pipeline constant id is specified for b
+{ fragmentConstants: { a: 1 }, _success: false }, // pipeline constant id is specified for a
+{ fragmentConstants: { 1: 1, b: 1 }, _success: false }, // pipeline constant id is specified for b
+{ fragmentConstants: { 数: 1 }, _success: true }, // test non-ASCII
+{ fragmentConstants: { séquençage: 0 }, _success: false } // test unicode is not normalized
+])
+).
+fn((t) => {
+ const { isAsync, fragmentConstants, _success } = t.params;
+
+ const descriptor = t.getDescriptor({
+ fragmentShaderCode: `
+ override r: f32 = 0.0;
+ override g: f32 = 0.0;
+ override 数: f32 = 0.0;
+ override sequencage: f32 = 0.0;
+ @id(1) override b: f32 = 0.0;
+ @id(1000) override a: f32 = 0.0;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(r, g, b, a + 数 + sequencage);
+ }`,
+ fragmentConstants
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('uninitialized,vertex').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for uninitialized overridable constants in vertex state.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ vertexConstants: {}, _success: false },
+{ vertexConstants: { x: 1, y: 1 }, _success: false }, // z is missing
+{ vertexConstants: { x: 1, z: 1 }, _success: true },
+{ vertexConstants: { x: 1, y: 1, z: 1, w: 1 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, vertexConstants, _success } = t.params;
+
+ t.doCreateRenderPipelineTest(isAsync, _success, {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ override x: f32;
+ override y: f32 = 0.0;
+ override z: f32;
+ override w: f32 = 1.0;
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(x, y, z, w);
+ }`
+ }),
+ entryPoint: 'main',
+ constants: vertexConstants
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+});
+
+g.test('uninitialized,fragment').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for uninitialized overridable constants in fragment state.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ fragmentConstants: {}, _success: false },
+{ fragmentConstants: { r: 1, g: 1 }, _success: false }, // b is missing
+{ fragmentConstants: { r: 1, b: 1 }, _success: true },
+{ fragmentConstants: { r: 1, g: 1, b: 1, a: 1 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, fragmentConstants, _success } = t.params;
+
+ const descriptor = t.getDescriptor({
+ fragmentShaderCode: `
+ override r: f32;
+ override g: f32 = 0.0;
+ override b: f32;
+ override a: f32 = 0.0;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(r, g, b, a);
+ }
+ `,
+ fragmentConstants
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('value,type_error,vertex').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for invalid constant values like inf, NaN will results in TypeError.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ vertexConstants: { cf: 1 }, _success: true }, // control
+{ vertexConstants: { cf: NaN }, _success: false },
+{ vertexConstants: { cf: Number.POSITIVE_INFINITY }, _success: false },
+{ vertexConstants: { cf: Number.NEGATIVE_INFINITY }, _success: false }]
+)
+).
+fn((t) => {
+ const { isAsync, vertexConstants, _success } = t.params;
+
+ t.doCreateRenderPipelineTest(
+ isAsync,
+ _success,
+ {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ override cf: f32 = 0.0;
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ _ = cf;
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ constants: vertexConstants
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ },
+ 'TypeError'
+ );
+});
+
+g.test('value,type_error,fragment').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for invalid constant values like inf, NaN will results in TypeError.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ fragmentConstants: { cf: 1 }, _success: true }, // control
+{ fragmentConstants: { cf: NaN }, _success: false },
+{ fragmentConstants: { cf: Number.POSITIVE_INFINITY }, _success: false },
+{ fragmentConstants: { cf: Number.NEGATIVE_INFINITY }, _success: false }]
+)
+).
+fn((t) => {
+ const { isAsync, fragmentConstants, _success } = t.params;
+
+ const descriptor = t.getDescriptor({
+ fragmentShaderCode: `
+ override cf: f32 = 0.0;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ _ = cf;
+ return vec4<f32>(1.0, 1.0, 1.0, 1.0);
+ }
+ `,
+ fragmentConstants
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor, 'TypeError');
+});
+
+g.test('value,validation_error,vertex').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for unrepresentable constant values in vertex stage.
+
+TODO(#2060): test with last_f64_castable.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ vertexConstants: { cu: kValue.u32.min }, _success: true },
+{ vertexConstants: { cu: kValue.u32.min - 1 }, _success: false },
+{ vertexConstants: { cu: kValue.u32.max }, _success: true },
+{ vertexConstants: { cu: kValue.u32.max + 1 }, _success: false },
+{ vertexConstants: { ci: kValue.i32.negative.min }, _success: true },
+{ vertexConstants: { ci: kValue.i32.negative.min - 1 }, _success: false },
+{ vertexConstants: { ci: kValue.i32.positive.max }, _success: true },
+{ vertexConstants: { ci: kValue.i32.positive.max + 1 }, _success: false },
+{ vertexConstants: { cf: kValue.f32.negative.min }, _success: true },
+{
+ vertexConstants: { cf: kValue.f32.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{ vertexConstants: { cf: kValue.f32.positive.max }, _success: true },
+{
+ vertexConstants: { cf: kValue.f32.positive.first_non_castable_pipeline_override },
+ _success: false
+},
+// Conversion to boolean can't fail
+{ vertexConstants: { cb: Number.MAX_VALUE }, _success: true },
+{ vertexConstants: { cb: kValue.i32.negative.min - 1 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, vertexConstants, _success } = t.params;
+
+ t.doCreateRenderPipelineTest(isAsync, _success, {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ override cb: bool = false;
+ override cu: u32 = 0u;
+ override ci: i32 = 0;
+ override cf: f32 = 0.0;
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ _ = cb;
+ _ = cu;
+ _ = ci;
+ _ = cf;
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ constants: vertexConstants
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+});
+
+g.test('value,validation_error,fragment').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for unrepresentable constant values in fragment stage.
+
+TODO(#2060): test with last_f64_castable.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ fragmentConstants: { cu: kValue.u32.min }, _success: true },
+{ fragmentConstants: { cu: kValue.u32.min - 1 }, _success: false },
+{ fragmentConstants: { cu: kValue.u32.max }, _success: true },
+{ fragmentConstants: { cu: kValue.u32.max + 1 }, _success: false },
+{ fragmentConstants: { ci: kValue.i32.negative.min }, _success: true },
+{ fragmentConstants: { ci: kValue.i32.negative.min - 1 }, _success: false },
+{ fragmentConstants: { ci: kValue.i32.positive.max }, _success: true },
+{ fragmentConstants: { ci: kValue.i32.positive.max + 1 }, _success: false },
+{ fragmentConstants: { cf: kValue.f32.negative.min }, _success: true },
+{
+ fragmentConstants: { cf: kValue.f32.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{ fragmentConstants: { cf: kValue.f32.positive.max }, _success: true },
+{
+ fragmentConstants: { cf: kValue.f32.positive.first_non_castable_pipeline_override },
+ _success: false
+},
+// Conversion to boolean can't fail
+{ fragmentConstants: { cb: Number.MAX_VALUE }, _success: true },
+{ fragmentConstants: { cb: kValue.i32.negative.min - 1 }, _success: true }]
+)
+).
+fn((t) => {
+ const { isAsync, fragmentConstants, _success } = t.params;
+
+ const descriptor = t.getDescriptor({
+ fragmentShaderCode: `
+ override cb: bool = false;
+ override cu: u32 = 0u;
+ override ci: i32 = 0;
+ override cf: f32 = 0.0;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ _ = cb;
+ _ = cu;
+ _ = ci;
+ _ = cf;
+ return vec4<f32>(1.0, 1.0, 1.0, 1.0);
+ }
+ `,
+ fragmentConstants
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('value,validation_error,f16,vertex').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for unrepresentable f16 constant values in vertex stage.
+
+TODO(#2060): Tighten the cases around the valid/invalid boundary once we have WGSL spec
+clarity on whether values like f16.positive.last_f64_castable would be valid. See issue.
+`
+).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ vertexConstants: { cf16: kValue.f16.negative.min }, _success: true },
+{
+ vertexConstants: { cf16: kValue.f16.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{ vertexConstants: { cf16: kValue.f16.positive.max }, _success: true },
+{
+ vertexConstants: { cf16: kValue.f16.positive.first_non_castable_pipeline_override },
+ _success: false
+},
+{ vertexConstants: { cf16: kValue.f32.negative.min }, _success: false },
+{ vertexConstants: { cf16: kValue.f32.positive.max }, _success: false },
+{
+ vertexConstants: { cf16: kValue.f32.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{
+ vertexConstants: { cf16: kValue.f32.positive.first_non_castable_pipeline_override },
+ _success: false
+}]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn((t) => {
+ const { isAsync, vertexConstants, _success } = t.params;
+
+ t.doCreateRenderPipelineTest(isAsync, _success, {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ enable f16;
+
+ override cf16: f16 = 0.0h;
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ _ = cf16;
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ constants: vertexConstants
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+});
+
+g.test('value,validation_error,f16,fragment').
+desc(
+ `
+Tests calling createRenderPipeline(Async) validation for unrepresentable f16 constant values in fragment stage.
+
+TODO(#2060): Tighten the cases around the valid/invalid boundary once we have WGSL spec
+clarity on whether values like f16.positive.last_f64_castable would be valid. See issue.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u //
+.combine('isAsync', [true, false]).
+combineWithParams([
+{ fragmentConstants: { cf16: kValue.f16.negative.min }, _success: true },
+{
+ fragmentConstants: { cf16: kValue.f16.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{ fragmentConstants: { cf16: kValue.f16.positive.max }, _success: true },
+{
+ fragmentConstants: { cf16: kValue.f16.positive.first_non_castable_pipeline_override },
+ _success: false
+},
+{ fragmentConstants: { cf16: kValue.f32.negative.min }, _success: false },
+{ fragmentConstants: { cf16: kValue.f32.positive.max }, _success: false },
+{
+ fragmentConstants: { cf16: kValue.f32.negative.first_non_castable_pipeline_override },
+ _success: false
+},
+{
+ fragmentConstants: { cf16: kValue.f32.positive.first_non_castable_pipeline_override },
+ _success: false
+}]
+)
+).
+fn((t) => {
+ const { isAsync, fragmentConstants, _success } = t.params;
+
+ const descriptor = t.getDescriptor({
+ fragmentShaderCode: `
+ enable f16;
+
+ override cf16: f16 = 0.0h;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ _ = cf16;
+ return vec4<f32>(1.0, 1.0, 1.0, 1.0);
+ }
+ `,
+ fragmentConstants
+ });
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/primitive_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/primitive_state.spec.js
new file mode 100644
index 0000000000..249159a478
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/primitive_state.spec.js
@@ -0,0 +1,42 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests validation of GPUPrimitiveState of createRenderPipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kPrimitiveTopology, kIndexFormat } from '../../../capability_info.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+g.test('strip_index_format').
+desc(
+ `If primitive.topology is not "line-strip" or "triangle-strip", primitive.stripIndexFormat must be undefined.`
+).
+params((u) =>
+u.
+combine('isAsync', [false, true]).
+combine('topology', [undefined, ...kPrimitiveTopology]).
+combine('stripIndexFormat', [undefined, ...kIndexFormat])
+).
+fn((t) => {
+ const { isAsync, topology, stripIndexFormat } = t.params;
+
+ const descriptor = t.getDescriptor({ primitive: { topology, stripIndexFormat } });
+
+ const _success =
+ topology === 'line-strip' || topology === 'triangle-strip' || stripIndexFormat === undefined;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('unclipped_depth').
+desc(`If primitive.unclippedDepth is true, features must contain "depth-clip-control".`).
+params((u) => u.combine('isAsync', [false, true]).combine('unclippedDepth', [false, true])).
+fn((t) => {
+ const { isAsync, unclippedDepth } = t.params;
+
+ const descriptor = t.getDescriptor({ primitive: { unclippedDepth } });
+
+ const _success = !unclippedDepth || t.device.features.has('depth-clip-control');
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/shader_module.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/shader_module.spec.js
new file mode 100644
index 0000000000..72b44e6811
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/shader_module.spec.js
@@ -0,0 +1,112 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests createRenderPipeline validation issues related to the shader modules.
+
+Note: entry point matching tests are in ../shader_module/entry_point.spec.ts
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ getFragmentShaderCodeWithOutput,
+ kDefaultVertexShaderCode,
+ kDefaultFragmentShaderCode } from
+'../../../util/shader.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+const values = [0, 1, 0, 1];
+
+g.test('device_mismatch').
+desc(
+ 'Tests createRenderPipeline(Async) cannot be called with a shader module created from another device'
+).
+paramsSubcasesOnly((u) =>
+u.combine('isAsync', [true, false]).combineWithParams([
+{ vertex_mismatched: false, fragment_mismatched: false, _success: true },
+{ vertex_mismatched: true, fragment_mismatched: false, _success: false },
+{ vertex_mismatched: false, fragment_mismatched: true, _success: false }]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { isAsync, vertex_mismatched, fragment_mismatched, _success } = t.params;
+
+ const code = `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }
+ `;
+
+ const descriptor = {
+ vertex: {
+ module: vertex_mismatched ?
+ t.mismatchedDevice.createShaderModule({ code }) :
+ t.device.createShaderModule({ code }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: fragment_mismatched ?
+ t.mismatchedDevice.createShaderModule({
+ code: getFragmentShaderCodeWithOutput([
+ { values, plainType: 'f32', componentCount: 4 }]
+ )
+ }) :
+ t.device.createShaderModule({
+ code: getFragmentShaderCodeWithOutput([
+ { values, plainType: 'f32', componentCount: 4 }]
+ )
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ layout: t.getPipelineLayout()
+ };
+
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('invalid,vertex').
+desc(`Tests shader module must be valid.`).
+params((u) => u.combine('isAsync', [true, false]).combine('isVertexShaderValid', [true, false])).
+fn((t) => {
+ const { isAsync, isVertexShaderValid } = t.params;
+ t.doCreateRenderPipelineTest(isAsync, isVertexShaderValid, {
+ layout: 'auto',
+ vertex: {
+ module: isVertexShaderValid ?
+ t.device.createShaderModule({
+ code: kDefaultVertexShaderCode
+ }) :
+ t.createInvalidShaderModule(),
+ entryPoint: 'main'
+ }
+ });
+});
+
+g.test('invalid,fragment').
+desc(`Tests shader module must be valid.`).
+params((u) => u.combine('isAsync', [true, false]).combine('isFragmentShaderValid', [true, false])).
+fn((t) => {
+ const { isAsync, isFragmentShaderValid } = t.params;
+ t.doCreateRenderPipelineTest(isAsync, isFragmentShaderValid, {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kDefaultVertexShaderCode
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: isFragmentShaderValid ?
+ t.device.createShaderModule({
+ code: kDefaultFragmentShaderCode
+ }) :
+ t.createInvalidShaderModule(),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/vertex_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/vertex_state.spec.js
new file mode 100644
index 0000000000..c0e23749f9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/render_pipeline/vertex_state.spec.js
@@ -0,0 +1,765 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This test dedicatedly tests validation of GPUVertexState of createRenderPipeline.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ filterUniqueValueTestVariants,
+ makeValueTestVariant } from
+'../../../../common/util/util.js';
+import { kVertexFormats, kVertexFormatInfo } from '../../../capability_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+const VERTEX_SHADER_CODE_WITH_NO_INPUT = `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+ }
+`;
+
+function addTestAttributes(
+attributes,
+{
+ testAttribute,
+ testAttributeAtStart = true,
+ extraAttributeCount = 0,
+ extraAttributeSkippedLocations = []
+
+
+
+
+
+})
+{
+ // Add a bunch of dummy attributes each with a different location such that none of the locations
+ // are in extraAttributeSkippedLocations
+ let currentLocation = 0;
+ let extraAttribsAdded = 0;
+ while (extraAttribsAdded !== extraAttributeCount) {
+ if (extraAttributeSkippedLocations.includes(currentLocation)) {
+ currentLocation++;
+ continue;
+ }
+
+ attributes.push({ format: 'float32', shaderLocation: currentLocation, offset: 0 });
+ currentLocation++;
+ extraAttribsAdded++;
+ }
+
+ // Add the test attribute at the start or the end of the attributes.
+ if (testAttribute) {
+ if (testAttributeAtStart) {
+ attributes.unshift(testAttribute);
+ } else {
+ attributes.push(testAttribute);
+ }
+ }
+}
+
+class F extends ValidationTest {
+ getDescriptor(
+ buffers,
+ vertexShaderCode)
+ {
+ const descriptor = {
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({ code: vertexShaderCode }),
+ entryPoint: 'main',
+ buffers
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ };
+ return descriptor;
+ }
+
+ testVertexState(
+ success,
+ buffers,
+ vertexShader = VERTEX_SHADER_CODE_WITH_NO_INPUT)
+ {
+ const vsModule = this.device.createShaderModule({ code: vertexShader });
+ const fsModule = this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`
+ });
+
+ this.expectValidationError(() => {
+ this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: vsModule,
+ entryPoint: 'main',
+ buffers
+ },
+ fragment: {
+ module: fsModule,
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+ }, !success);
+ }
+
+ generateTestVertexShader(inputs) {
+ let interfaces = '';
+ let body = '';
+
+ let count = 0;
+ for (const input of inputs) {
+ interfaces += `@location(${input.location}) input${count} : ${input.type},\n`;
+ body += `var i${count} : ${input.type} = input.input${count};\n`;
+ count++;
+ }
+
+ return `
+ struct Inputs {
+ ${interfaces}
+ };
+ @vertex fn main(input : Inputs) -> @builtin(position) vec4<f32> {
+ ${body}
+ return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+ }
+ `;
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('max_vertex_buffer_limit').
+desc(
+ `Test that only up to <maxVertexBuffers> vertex buffers are allowed.
+ - Tests with 0, 1, limits, limits + 1 vertex buffers.
+ - Tests with the last buffer having an attribute or not.
+ This also happens to test that vertex buffers with no attributes are allowed and that a vertex state with no buffers is allowed.`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('countVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: 0 },
+{ mult: 1, add: 1 }]
+).
+combine('lastEmpty', [false, true])
+).
+fn((t) => {
+ const { countVariant, lastEmpty } = t.params;
+ const count = t.makeLimitVariant('maxVertexBuffers', countVariant);
+ const vertexBuffers = [];
+ for (let i = 0; i < count; i++) {
+ if (lastEmpty || i !== count - 1) {
+ vertexBuffers.push({ attributes: [], arrayStride: 0 });
+ } else {
+ vertexBuffers.push({
+ attributes: [{ format: 'float32', offset: 0, shaderLocation: 0 }],
+ arrayStride: 0
+ });
+ }
+ }
+
+ const success = count <= t.device.limits.maxVertexBuffers;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('max_vertex_attribute_limit').
+desc(
+ `Test that only up to <maxVertexAttributes> vertex attributes are allowed.
+ - Tests with 0, 1, limit, limits + 1 vertex attribute.
+ - Tests with 0, 1, 4 attributes per buffer (with remaining attributes in the last buffer).`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('attribCountVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: 0 },
+{ mult: 1, add: 1 }]
+).
+combine('attribsPerBuffer', [0, 1, 4])
+).
+fn((t) => {
+ const { attribCountVariant, attribsPerBuffer } = t.params;
+ const attribCount = t.makeLimitVariant('maxVertexAttributes', attribCountVariant);
+
+ const vertexBuffers = [];
+
+ let attribsAdded = 0;
+ while (attribsAdded !== attribCount) {
+ // Choose how many attributes to add for this buffer. The last buffer gets all remaining attributes.
+ let targetCount = Math.min(attribCount, attribsAdded + attribsPerBuffer);
+ if (vertexBuffers.length === t.device.limits.maxVertexBuffers - 1) {
+ targetCount = attribCount;
+ }
+
+ const attributes = [];
+ while (attribsAdded !== targetCount) {
+ attributes.push({ format: 'float32', offset: 0, shaderLocation: attribsAdded });
+ attribsAdded++;
+ }
+
+ vertexBuffers.push({ arrayStride: 0, attributes });
+ }
+
+ const success = attribCount <= t.device.limits.maxVertexAttributes;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('max_vertex_buffer_array_stride_limit').
+desc(
+ `Test that the vertex buffer arrayStride must be at most <maxVertexBufferArrayStride>.
+ - Test for various vertex buffer indices
+ - Test for array strides 0, 4, 256, limit - 4, limit, limit + 4`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('vertexBufferIndexVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('arrayStrideVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 4 },
+{ mult: 0, add: 256 },
+{ mult: 1, add: -4 },
+{ mult: 1, add: 0 },
+{ mult: 1, add: +4 }]
+)
+).
+fn((t) => {
+ const { vertexBufferIndexVariant, arrayStrideVariant } = t.params;
+ const vertexBufferIndex = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexVariant);
+ const arrayStride = t.makeLimitVariant('maxVertexBufferArrayStride', arrayStrideVariant);
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndex] = { arrayStride, attributes: [] };
+
+ const success = arrayStride <= t.device.limits.maxVertexBufferArrayStride;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('vertex_buffer_array_stride_limit_alignment').
+desc(
+ `Test that the vertex buffer arrayStride must be a multiple of 4 (including 0).
+ - Test for various vertex buffer indices
+ - Test for array strides 0, 1, 2, 4, limit - 4, limit - 2, limit`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('vertexBufferIndexVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('arrayStrideVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 0, add: 2 },
+{ mult: 0, add: 4 },
+{ mult: 1, add: -4 },
+{ mult: 1, add: -2 },
+{ mult: 1, add: 0 }]
+)
+).
+fn((t) => {
+ const { vertexBufferIndexVariant, arrayStrideVariant } = t.params;
+ const vertexBufferIndex = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexVariant);
+ const arrayStride = t.makeLimitVariant('maxVertexBufferArrayStride', arrayStrideVariant);
+
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndex] = { arrayStride, attributes: [] };
+
+ const success = arrayStride % 4 === 0;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('vertex_attribute_shaderLocation_limit').
+desc(
+ `Test shaderLocation must be less than maxVertexAttributes.
+ - Test for various vertex buffer indices
+ - Test for various amounts of attributes in that vertex buffer
+ - Test for shaderLocation 0, 1, limit - 1, limit`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('vertexBufferIndexVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('extraAttributeCountVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('testAttributeAtStart', [false, true]).
+combine('testShaderLocationVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 },
+{ mult: 1, add: 0 }]
+)
+).
+fn((t) => {
+ const {
+ vertexBufferIndexVariant,
+ extraAttributeCountVariant,
+ testShaderLocationVariant,
+ testAttributeAtStart
+ } = t.params;
+ const vertexBufferIndex = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexVariant);
+ const extraAttributeCount = t.makeLimitVariant(
+ 'maxVertexAttributes',
+ extraAttributeCountVariant
+ );
+ const testShaderLocation = t.makeLimitVariant('maxVertexAttributes', testShaderLocationVariant);
+
+ const attributes = [];
+ addTestAttributes(attributes, {
+ testAttribute: { format: 'float32', offset: 0, shaderLocation: testShaderLocation },
+ testAttributeAtStart,
+ extraAttributeCount,
+ extraAttributeSkippedLocations: [testShaderLocation]
+ });
+
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndex] = { arrayStride: 256, attributes };
+
+ const success = testShaderLocation < t.device.limits.maxVertexAttributes;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('vertex_attribute_shaderLocation_unique').
+desc(
+ `Test that shaderLocation must be unique in the vertex state.
+ - Test for various pairs of buffers that contain the potentially conflicting attributes
+ - Test for the potentially conflicting attributes in various places in the buffers (with dummy attributes)
+ - Test for various shaderLocations that conflict or not`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('vertexBufferIndexAVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('vertexBufferIndexBVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('testAttributeAtStartA', [false, true]).
+combine('testAttributeAtStartB', [false, true]).
+combine('shaderLocationAVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 0, add: 7 },
+{ mult: 1, add: -1 }]
+).
+combine('shaderLocationBVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 0, add: 7 },
+{ mult: 1, add: -1 }]
+).
+combine('extraAttributeCount', [0, 4])
+).
+fn((t) => {
+ const {
+ vertexBufferIndexAVariant,
+ vertexBufferIndexBVariant,
+ testAttributeAtStartA,
+ testAttributeAtStartB,
+ shaderLocationAVariant,
+ shaderLocationBVariant,
+ extraAttributeCount
+ } = t.params;
+ const vertexBufferIndexA = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexAVariant);
+ const vertexBufferIndexB = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexBVariant);
+ const shaderLocationA = t.makeLimitVariant('maxVertexAttributes', shaderLocationAVariant);
+ const shaderLocationB = t.makeLimitVariant('maxVertexAttributes', shaderLocationBVariant);
+
+ // Depending on the params, the vertexBuffer for A and B can be the same or different. To support
+ // both cases without code changes we treat `vertexBufferAttributes` as a map from indices to
+ // vertex buffer descriptors, with A and B potentially reusing the same JS object if they have the
+ // same index.
+ const vertexBufferAttributes = [];
+ vertexBufferAttributes[vertexBufferIndexA] = [];
+ vertexBufferAttributes[vertexBufferIndexB] = [];
+
+ // Add the dummy attributes for attribute A
+ const attributesA = vertexBufferAttributes[vertexBufferIndexA];
+ addTestAttributes(attributesA, {
+ testAttribute: { format: 'float32', offset: 0, shaderLocation: shaderLocationA },
+ testAttributeAtStart: testAttributeAtStartA,
+ extraAttributeCount,
+ extraAttributeSkippedLocations: [shaderLocationA, shaderLocationB]
+ });
+
+ // Add attribute B. Not that attributesB can be the same object as attributesA so they end
+ // up in the same vertex buffer.
+ const attributesB = vertexBufferAttributes[vertexBufferIndexB];
+ addTestAttributes(attributesB, {
+ testAttribute: { format: 'float32', offset: 0, shaderLocation: shaderLocationB },
+ testAttributeAtStart: testAttributeAtStartB
+ });
+
+ // Use the attributes to make the list of vertex buffers. Note that we might be setting the same vertex
+ // buffer twice, but that only happens when it is the only vertex buffer.
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndexA] = { arrayStride: 256, attributes: attributesA };
+ vertexBuffers[vertexBufferIndexB] = { arrayStride: 256, attributes: attributesB };
+
+ // Note that an empty vertex shader will be used so errors only happens because of the conflict
+ // in the vertex state.
+ const success = shaderLocationA !== shaderLocationB;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('vertex_shader_input_location_limit').
+desc(
+ `Test that vertex shader's input's location decoration must be less than maxVertexAttributes.
+ - Test for shaderLocation 0, 1, limit - 1, limit, MAX_I32 (the WGSL spec requires a non-negative i32)`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('testLocationVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 },
+{ mult: 1, add: 0 },
+{ mult: 0, add: 2 ** 31 - 1 }]
+)
+).
+fn((t) => {
+ const { testLocationVariant } = t.params;
+ const testLocation = t.makeLimitVariant('maxVertexAttributes', testLocationVariant);
+
+ const shader = t.generateTestVertexShader([
+ {
+ type: 'vec4<f32>',
+ location: testLocation
+ }]
+ );
+
+ const vertexBuffers = [
+ {
+ arrayStride: 512,
+ attributes: [
+ {
+ format: 'float32',
+ offset: 0,
+ shaderLocation: testLocation
+ }]
+
+ }];
+
+
+ const success = testLocation < t.device.limits.maxVertexAttributes;
+ t.testVertexState(success, vertexBuffers, shader);
+});
+
+g.test('vertex_shader_input_location_in_vertex_state').
+desc(
+ `Test that a vertex shader defined in the shader must have a corresponding attribute in the vertex state.
+ - Test for various input locations.
+ - Test for the attribute in various places in the list of vertex buffer and various places inside the vertex buffer descriptor`
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('vertexBufferIndexVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('extraAttributeCountVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('testAttributeAtStart', [false, true]).
+combine('testShaderLocationVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 0, add: 4 },
+{ mult: 0, add: 5 },
+{ mult: 1, add: -1 }]
+)
+).
+fn((t) => {
+ const {
+ vertexBufferIndexVariant,
+ extraAttributeCountVariant,
+ testAttributeAtStart,
+ testShaderLocationVariant
+ } = t.params;
+ const vertexBufferIndex = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexVariant);
+ const extraAttributeCount = t.makeLimitVariant(
+ 'maxVertexAttributes',
+ extraAttributeCountVariant
+ );
+ const testShaderLocation = t.makeLimitVariant('maxVertexAttributes', testShaderLocationVariant);
+ // We have a shader using `testShaderLocation`.
+ const shader = t.generateTestVertexShader([
+ {
+ type: 'vec4<f32>',
+ location: testShaderLocation
+ }]
+ );
+
+ const attributes = [];
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndex] = { arrayStride: 256, attributes };
+
+ // Fill attributes with a bunch of attributes for other locations.
+ // Using that vertex state is invalid because the vertex state doesn't contain the test location
+ addTestAttributes(attributes, {
+ extraAttributeCount,
+ extraAttributeSkippedLocations: [testShaderLocation]
+ });
+ t.testVertexState(false, vertexBuffers, shader);
+
+ // Add an attribute for the test location and try again.
+ addTestAttributes(attributes, {
+ testAttribute: { format: 'float32', shaderLocation: testShaderLocation, offset: 0 },
+ testAttributeAtStart
+ });
+ t.testVertexState(true, vertexBuffers, shader);
+});
+
+g.test('vertex_shader_type_matches_attribute_format').
+desc(
+ `
+ Test that the vertex shader declaration must have a type compatible with the vertex format.
+ - Test for all formats.
+ - Test for all combinations of u/i/f32 with and without vectors.`
+).
+params((u) =>
+u.
+combine('format', kVertexFormats).
+beginSubcases().
+combine('shaderBaseType', ['u32', 'i32', 'f32']).
+expand('shaderType', (p) => [
+p.shaderBaseType,
+`vec2<${p.shaderBaseType}>`,
+`vec3<${p.shaderBaseType}>`,
+`vec4<${p.shaderBaseType}>`]
+)
+).
+fn((t) => {
+ const { format, shaderBaseType, shaderType } = t.params;
+ const shader = t.generateTestVertexShader([
+ {
+ type: shaderType,
+ location: 0
+ }]
+ );
+
+ const requiredBaseType = {
+ sint: 'i32',
+ uint: 'u32',
+ snorm: 'f32',
+ unorm: 'f32',
+ float: 'f32'
+ }[kVertexFormatInfo[format].type];
+
+ const success = requiredBaseType === shaderBaseType;
+ t.testVertexState(
+ success,
+ [
+ {
+ arrayStride: 0,
+ attributes: [{ offset: 0, shaderLocation: 0, format }]
+ }],
+
+ shader
+ );
+});
+
+g.test('vertex_attribute_offset_alignment').
+desc(
+ `
+ Test that vertex attribute offsets must be aligned to the format's component byte size.
+ - Test for all formats.
+ - Test for various arrayStrides and offsets within that stride
+ - Test for various vertex buffer indices
+ - Test for various amounts of attributes in that vertex buffer`
+).
+params((u) =>
+u.
+combine('format', kVertexFormats).
+combine('arrayStrideVariant', [
+{ mult: 0, add: 256 },
+{ mult: 1, add: 0 }]
+).
+expand('offsetVariant', (p) => {
+ const formatSize = kVertexFormatInfo[p.format].byteSize;
+ return filterUniqueValueTestVariants([
+ { mult: 0, add: 0 },
+ { mult: 0, add: Math.floor(formatSize / 2) },
+ { mult: 0, add: formatSize },
+ { mult: 0, add: 2 },
+ { mult: 0, add: 4 },
+ { mult: 1, add: -formatSize },
+ { mult: 1, add: -formatSize - Math.floor(formatSize / 2) },
+ { mult: 1, add: -formatSize - 4 },
+ { mult: 1, add: -formatSize - 2 }]
+ );
+}).
+beginSubcases().
+combine('vertexBufferIndexVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('extraAttributeCountVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('testAttributeAtStart', [false, true])
+).
+fn((t) => {
+ const {
+ format,
+ arrayStrideVariant,
+ offsetVariant,
+ vertexBufferIndexVariant,
+ extraAttributeCountVariant,
+ testAttributeAtStart
+ } = t.params;
+ const arrayStride = t.makeLimitVariant('maxVertexBufferArrayStride', arrayStrideVariant);
+ const vertexBufferIndex = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexVariant);
+ const extraAttributeCount = t.makeLimitVariant(
+ 'maxVertexAttributes',
+ extraAttributeCountVariant
+ );
+ const offset = makeValueTestVariant(arrayStride, offsetVariant);
+
+ const attributes = [];
+ addTestAttributes(attributes, {
+ testAttribute: { format, offset, shaderLocation: 0 },
+ testAttributeAtStart,
+ extraAttributeCount,
+ extraAttributeSkippedLocations: [0]
+ });
+
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndex] = { arrayStride, attributes };
+
+ const formatInfo = kVertexFormatInfo[format];
+ const formatSize = formatInfo.byteSize;
+ const success = offset % Math.min(4, formatSize) === 0;
+
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('vertex_attribute_contained_in_stride').
+desc(
+ `
+ Test that vertex attribute [offset, offset + formatSize) must be contained in the arrayStride if arrayStride is not 0:
+ - Test for all formats.
+ - Test for various arrayStrides and offsets within that stride
+ - Test for various vertex buffer indices
+ - Test for various amounts of attributes in that vertex buffer`
+).
+params((u) =>
+u.
+combine('format', kVertexFormats).
+beginSubcases().
+combine('arrayStrideVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 256 },
+{ mult: 1, add: -4 },
+{ mult: 1, add: 0 }]
+).
+expand('offsetVariant', function* (p) {
+ // Compute a bunch of test offsets to test.
+ const formatSize = kVertexFormatInfo[p.format].byteSize;
+ yield { mult: 0, add: 0 };
+ yield { mult: 0, add: 4 };
+ yield { mult: 1, add: -formatSize };
+ yield { mult: 1, add: -formatSize + 4 };
+
+ // Avoid adding duplicate cases when formatSize == 4 (it is already tested above)
+ if (formatSize !== 4) {
+ yield { mult: 0, add: formatSize };
+ yield { mult: 1, add: 0 };
+ }
+}).
+combine('vertexBufferIndexVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('extraAttributeCountVariant', [
+{ mult: 0, add: 0 },
+{ mult: 0, add: 1 },
+{ mult: 1, add: -1 }]
+).
+combine('testAttributeAtStart', [false, true])
+).
+fn((t) => {
+ const {
+ format,
+ arrayStrideVariant,
+ offsetVariant,
+ vertexBufferIndexVariant,
+ extraAttributeCountVariant,
+ testAttributeAtStart
+ } = t.params;
+ const arrayStride = t.makeLimitVariant('maxVertexBufferArrayStride', arrayStrideVariant);
+ const vertexBufferIndex = t.makeLimitVariant('maxVertexBuffers', vertexBufferIndexVariant);
+ const extraAttributeCount = t.makeLimitVariant(
+ 'maxVertexAttributes',
+ extraAttributeCountVariant
+ );
+ // arrayStride = 0 is a special case because for the offset validation it acts the same
+ // as arrayStride = device.limits.maxVertexBufferArrayStride. We special case here so as to avoid adding
+ // negative offsets that would cause an IDL exception to be thrown instead of a validation
+ // error.
+ const stride = arrayStride !== 0 ? arrayStride : t.device.limits.maxVertexBufferArrayStride;
+ const offset = makeValueTestVariant(stride, offsetVariant);
+
+ const attributes = [];
+ addTestAttributes(attributes, {
+ testAttribute: { format, offset, shaderLocation: 0 },
+ testAttributeAtStart,
+ extraAttributeCount,
+ extraAttributeSkippedLocations: [0]
+ });
+
+ const vertexBuffers = [];
+ vertexBuffers[vertexBufferIndex] = { arrayStride, attributes };
+
+ const formatSize = kVertexFormatInfo[format].byteSize;
+ const limit = arrayStride === 0 ? t.device.limits.maxVertexBufferArrayStride : arrayStride;
+
+ const success = offset + formatSize <= limit;
+ t.testVertexState(success, vertexBuffers);
+});
+
+g.test('many_attributes_overlapping').
+desc(`Test that it is valid to have many vertex attributes overlap`).
+fn((t) => {
+ // Create many attributes, each of them intersects with at least 3 others.
+ const attributes = [];
+ const formats = ['float32x4', 'uint32x4', 'sint32x4'];
+ for (let i = 0; i < t.device.limits.maxVertexAttributes; i++) {
+ attributes.push({ format: formats[i % 3], offset: i * 4, shaderLocation: i });
+ }
+
+ t.testVertexState(true, [{ arrayStride: 0, attributes }]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.js
new file mode 100644
index 0000000000..d4a6fac4da
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_encoder.spec.js
@@ -0,0 +1,928 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Buffer Usages Validation Tests in Render Pass and Compute Pass.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { assert, unreachable } from '../../../../../common/util/util.js';
+import { ValidationTest } from '../../validation_test.js';
+
+const kBoundBufferSize = 256;
+
+
+
+
+
+
+
+
+
+
+export const kAllBufferUsages = [
+'uniform',
+'storage',
+'read-only-storage',
+'vertex',
+'index',
+'indirect',
+'indexedIndirect'];
+
+
+export class BufferResourceUsageTest extends ValidationTest {
+ createBindGroupLayoutForTest(
+ type,
+ resourceVisibility)
+ {
+ const bindGroupLayoutEntry = {
+ binding: 0,
+ visibility:
+ resourceVisibility === 'compute' ? GPUShaderStage.COMPUTE : GPUShaderStage.FRAGMENT,
+ buffer: {
+ type
+ }
+ };
+ return this.device.createBindGroupLayout({
+ entries: [bindGroupLayoutEntry]
+ });
+ }
+
+ createBindGroupForTest(
+ buffer,
+ offset,
+ type,
+ resourceVisibility)
+ {
+ return this.device.createBindGroup({
+ layout: this.createBindGroupLayoutForTest(type, resourceVisibility),
+ entries: [
+ {
+ binding: 0,
+ resource: { buffer, offset, size: kBoundBufferSize }
+ }]
+
+ });
+ }
+
+ beginSimpleRenderPass(encoder) {
+ const colorTexture = this.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [16, 16, 1]
+ });
+ return encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+
+ createRenderPipelineForTest(
+ pipelineLayout,
+ vertexBufferCount)
+ {
+ const vertexBuffers = [];
+ for (let i = 0; i < vertexBufferCount; ++i) {
+ vertexBuffers.push({
+ arrayStride: 4,
+ attributes: [
+ {
+ format: 'float32',
+ shaderLocation: i,
+ offset: 0
+ }]
+
+ });
+ }
+
+ return this.device.createRenderPipeline({
+ layout: pipelineLayout,
+ vertex: {
+ module: this.device.createShaderModule({
+ code: this.getNoOpShaderCode('VERTEX')
+ }),
+ entryPoint: 'main',
+ buffers: vertexBuffers
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+ }
+}
+
+function IsBufferUsageInBindGroup(bufferUsage) {
+ switch (bufferUsage) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':
+ return true;
+ case 'vertex':
+ case 'index':
+ case 'indirect':
+ case 'indexedIndirect':
+ return false;
+ default:
+ unreachable();
+ }
+}
+
+export const g = makeTestGroup(BufferResourceUsageTest);
+
+g.test('subresources,buffer_usage_in_one_compute_pass_with_no_dispatch').
+desc(
+ `
+Test that it is always allowed to set multiple bind groups with same buffer in a compute pass
+encoder without any dispatch calls as state-setting compute pass commands, like setBindGroup(index,
+bindGroup, dynamicOffsets), do not contribute directly to a usage scope.`
+).
+params((u) =>
+u.
+combine('usage0', ['uniform', 'storage', 'read-only-storage']).
+combine('usage1', ['uniform', 'storage', 'read-only-storage']).
+beginSubcases().
+combine('visibility0', ['compute', 'fragment']).
+combine('visibility1', ['compute', 'fragment']).
+combine('hasOverlap', [true, false])
+).
+fn((t) => {
+ const { usage0, usage1, visibility0, visibility1, hasOverlap } = t.params;
+
+ const buffer = t.createBufferWithState('valid', {
+ size: kBoundBufferSize * 2,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const computePassEncoder = encoder.beginComputePass();
+
+ const offset0 = 0;
+ const bindGroup0 = t.createBindGroupForTest(buffer, offset0, usage0, visibility0);
+ computePassEncoder.setBindGroup(0, bindGroup0);
+
+ const offset1 = hasOverlap ? offset0 : kBoundBufferSize;
+ const bindGroup1 = t.createBindGroupForTest(buffer, offset1, usage1, visibility1);
+ computePassEncoder.setBindGroup(1, bindGroup1);
+
+ computePassEncoder.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, false);
+});
+
+g.test('subresources,buffer_usage_in_one_compute_pass_with_one_dispatch').
+desc(
+ `
+Test that when one buffer is used in one compute pass encoder, its list of internal usages within
+one usage scope can only be a compatible usage list. According to WebGPU SPEC, within one dispatch,
+for each bind group slot that is used by the current GPUComputePipeline's layout, every subresource
+referenced by that bind group is "used" in the usage scope.
+
+For both usage === storage, there is writable buffer binding aliasing so we skip this case and will
+have tests covered (https://github.com/gpuweb/cts/issues/2232)
+`
+).
+params((u) =>
+u.
+combine('usage0AccessibleInDispatch', [true, false]).
+combine('usage1AccessibleInDispatch', [true, false]).
+combine('dispatchBeforeUsage1', [true, false]).
+beginSubcases().
+combine('usage0', ['uniform', 'storage', 'read-only-storage', 'indirect']).
+combine('visibility0', ['compute', 'fragment']).
+filter((t) => {
+ // The buffer with `indirect` usage is always accessible in the dispatch call.
+ if (
+ t.usage0 === 'indirect' && (
+ !t.usage0AccessibleInDispatch || t.visibility0 !== 'compute' || !t.dispatchBeforeUsage1))
+ {
+ return false;
+ }
+ if (t.usage0AccessibleInDispatch && t.visibility0 !== 'compute') {
+ return false;
+ }
+ if (t.dispatchBeforeUsage1 && t.usage1AccessibleInDispatch) {
+ return false;
+ }
+ return true;
+}).
+combine('usage1', ['uniform', 'storage', 'read-only-storage', 'indirect']).
+combine('visibility1', ['compute', 'fragment']).
+filter((t) => {
+ if (
+ t.usage1 === 'indirect' && (
+ !t.usage1AccessibleInDispatch || t.visibility1 !== 'compute' || t.dispatchBeforeUsage1))
+ {
+ return false;
+ }
+ // When the first buffer usage is `indirect`, there has already been one dispatch call, so
+ // in this test we always make the second usage inaccessible in the dispatch call.
+ if (
+ t.usage1AccessibleInDispatch && (
+ t.visibility1 !== 'compute' || t.usage0 === 'indirect'))
+ {
+ return false;
+ }
+
+ // Avoid writable storage buffer bindings aliasing.
+ if (t.usage0 === 'storage' && t.usage1 === 'storage') {
+ return false;
+ }
+ return true;
+}).
+combine('hasOverlap', [true, false])
+).
+fn((t) => {
+ const {
+ usage0AccessibleInDispatch,
+ usage1AccessibleInDispatch,
+ dispatchBeforeUsage1,
+ usage0,
+ visibility0,
+ usage1,
+ visibility1,
+ hasOverlap
+ } = t.params;
+
+ const buffer = t.createBufferWithState('valid', {
+ size: kBoundBufferSize * 2,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const computePassEncoder = encoder.beginComputePass();
+
+ const offset0 = 0;
+ switch (usage0) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup0 = t.createBindGroupForTest(buffer, offset0, usage0, visibility0);
+ computePassEncoder.setBindGroup(0, bindGroup0);
+
+ /*
+ * setBindGroup(bindGroup0);
+ * dispatchWorkgroups();
+ * setBindGroup(bindGroup1);
+ */
+ if (dispatchBeforeUsage1) {
+ let pipelineLayout = undefined;
+ if (usage0AccessibleInDispatch) {
+ const bindGroupLayout0 = t.createBindGroupLayoutForTest(usage0, visibility0);
+ pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout0]
+ });
+ }
+ const computePipeline = t.createNoOpComputePipeline(pipelineLayout);
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroups(1);
+ }
+ break;
+ }
+ case 'indirect':{
+ /*
+ * dispatchWorkgroupsIndirect(buffer);
+ * setBindGroup(bindGroup1);
+ */
+ assert(dispatchBeforeUsage1);
+ const computePipeline = t.createNoOpComputePipeline();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroupsIndirect(buffer, offset0);
+ break;
+ }
+ }
+
+ const offset1 = hasOverlap ? offset0 : kBoundBufferSize;
+ switch (usage1) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup1 = t.createBindGroupForTest(buffer, offset1, usage1, visibility1);
+ const bindGroupIndex = usage0AccessibleInDispatch ? 1 : 0;
+ computePassEncoder.setBindGroup(bindGroupIndex, bindGroup1);
+
+ /*
+ * setBindGroup(bindGroup0);
+ * setBindGroup(bindGroup1);
+ * dispatchWorkgroups();
+ */
+ if (!dispatchBeforeUsage1) {
+ const bindGroupLayouts = [];
+ if (usage0AccessibleInDispatch && usage0 !== 'indirect') {
+ const bindGroupLayout0 = t.createBindGroupLayoutForTest(usage0, visibility0);
+ bindGroupLayouts.push(bindGroupLayout0);
+ }
+ if (usage1AccessibleInDispatch) {
+ const bindGroupLayout1 = t.createBindGroupLayoutForTest(usage1, visibility1);
+ bindGroupLayouts.push(bindGroupLayout1);
+ }
+ const pipelineLayout = bindGroupLayouts ?
+ t.device.createPipelineLayout({
+ bindGroupLayouts
+ }) :
+ undefined;
+ const computePipeline = t.createNoOpComputePipeline(pipelineLayout);
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroups(1);
+ }
+ break;
+ }
+ case 'indirect':{
+ /*
+ * setBindGroup(bindGroup0);
+ * dispatchWorkgroupsIndirect(buffer);
+ */
+ assert(!dispatchBeforeUsage1);
+ let pipelineLayout = undefined;
+ if (usage0AccessibleInDispatch) {
+ assert(usage0 !== 'indirect');
+ pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [t.createBindGroupLayoutForTest(usage0, visibility0)]
+ });
+ }
+ const computePipeline = t.createNoOpComputePipeline(pipelineLayout);
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroupsIndirect(buffer, offset1);
+ break;
+ }
+ }
+ computePassEncoder.end();
+
+ const usageHasConflict =
+ usage0 === 'storage' && usage1 !== 'storage' ||
+ usage0 !== 'storage' && usage1 === 'storage';
+ const fail =
+ usageHasConflict &&
+ visibility0 === 'compute' &&
+ visibility1 === 'compute' &&
+ usage0AccessibleInDispatch &&
+ usage1AccessibleInDispatch;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, fail);
+});
+
+g.test('subresources,buffer_usage_in_compute_pass_with_two_dispatches').
+desc(
+ `
+Test that it is always allowed to use one buffer in different dispatch calls as in WebGPU SPEC,
+within one dispatch, for each bind group slot that is used by the current GPUComputePipeline's
+layout, every subresource referenced by that bind group is "used" in the usage scope, and different
+dispatch calls refer to different usage scopes.`
+).
+params((u) =>
+u.
+combine('usage0', ['uniform', 'storage', 'read-only-storage', 'indirect']).
+combine('usage1', ['uniform', 'storage', 'read-only-storage', 'indirect']).
+beginSubcases().
+combine('inSamePass', [true, false]).
+combine('hasOverlap', [true, false])
+).
+fn((t) => {
+ const { usage0, usage1, inSamePass, hasOverlap } = t.params;
+
+ const UseBufferOnComputePassEncoder = (
+ computePassEncoder,
+ buffer,
+ usage,
+ offset) =>
+ {
+ switch (usage) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup = t.createBindGroupForTest(buffer, offset, usage, 'compute');
+ computePassEncoder.setBindGroup(0, bindGroup);
+
+ const bindGroupLayout = t.createBindGroupLayoutForTest(usage, 'compute');
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ });
+ const computePipeline = t.createNoOpComputePipeline(pipelineLayout);
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroups(1);
+ break;
+ }
+ case 'indirect':{
+ const computePipeline = t.createNoOpComputePipeline();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroupsIndirect(buffer, offset);
+ break;
+ }
+ default:
+ unreachable();
+ break;
+ }
+ };
+
+ const buffer = t.createBufferWithState('valid', {
+ size: kBoundBufferSize * 2,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const computePassEncoder = encoder.beginComputePass();
+
+ const offset0 = 0;
+ const offset1 = hasOverlap ? offset0 : kBoundBufferSize;
+ UseBufferOnComputePassEncoder(computePassEncoder, buffer, usage0, offset0);
+
+ if (inSamePass) {
+ UseBufferOnComputePassEncoder(computePassEncoder, buffer, usage1, offset1);
+ computePassEncoder.end();
+ } else {
+ computePassEncoder.end();
+ const anotherComputePassEncoder = encoder.beginComputePass();
+ UseBufferOnComputePassEncoder(anotherComputePassEncoder, buffer, usage1, offset1);
+ anotherComputePassEncoder.end();
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, false);
+});
+
+g.test('subresources,buffer_usage_in_one_render_pass_with_no_draw').
+desc(
+ `
+Test that when one buffer is used in one render pass encoder, its list of internal usages within one
+usage scope (all the commands in the whole render pass) can only be a compatible usage list even if
+there is no draw call in the render pass.
+ `
+).
+params((u) =>
+u.
+combine('usage0', ['uniform', 'storage', 'read-only-storage', 'vertex', 'index']).
+combine('usage1', ['uniform', 'storage', 'read-only-storage', 'vertex', 'index']).
+beginSubcases().
+combine('hasOverlap', [true, false]).
+combine('visibility0', ['compute', 'fragment']).
+unless((t) => t.visibility0 === 'compute' && !IsBufferUsageInBindGroup(t.usage0)).
+combine('visibility1', ['compute', 'fragment']).
+unless((t) => t.visibility1 === 'compute' && !IsBufferUsageInBindGroup(t.usage1))
+).
+fn((t) => {
+ const { usage0, usage1, hasOverlap, visibility0, visibility1 } = t.params;
+
+ const UseBufferOnRenderPassEncoder = (
+ buffer,
+ offset,
+ type,
+ bindGroupVisibility,
+ renderPassEncoder) =>
+ {
+ switch (type) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup = t.createBindGroupForTest(buffer, offset, type, bindGroupVisibility);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ break;
+ }
+ case 'vertex':{
+ renderPassEncoder.setVertexBuffer(0, buffer, offset, kBoundBufferSize);
+ break;
+ }
+ case 'index':{
+ renderPassEncoder.setIndexBuffer(buffer, 'uint16', offset, kBoundBufferSize);
+ break;
+ }
+ case 'indirect':
+ case 'indexedIndirect':
+ unreachable();
+ break;
+ }
+ };
+
+ const buffer = t.createBufferWithState('valid', {
+ size: kBoundBufferSize * 2,
+ usage:
+ GPUBufferUsage.UNIFORM |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+ const offset0 = 0;
+ UseBufferOnRenderPassEncoder(buffer, offset0, usage0, visibility0, renderPassEncoder);
+ const offset1 = hasOverlap ? offset0 : kBoundBufferSize;
+ UseBufferOnRenderPassEncoder(buffer, offset1, usage1, visibility1, renderPassEncoder);
+ renderPassEncoder.end();
+
+ const fail = usage0 === 'storage' !== (usage1 === 'storage');
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, fail);
+});
+
+g.test('subresources,buffer_usage_in_one_render_pass_with_one_draw').
+desc(
+ `
+Test that when one buffer is used in one render pass encoder where there is one draw call, its list
+of internal usages within one usage scope (all the commands in the whole render pass) can only be a
+compatible usage list. The usage scope rules are not related to the buffer offset or the bind group
+layout visibilities.
+
+For both usage === storage, there is writable buffer binding aliasing so we skip this case and will
+have tests covered (https://github.com/gpuweb/cts/issues/2232)
+`
+).
+params((u) =>
+u.
+combine('usage0', kAllBufferUsages).
+combine('usage1', kAllBufferUsages).
+beginSubcases().
+combine('usage0AccessibleInDraw', [true, false]).
+combine('usage1AccessibleInDraw', [true, false]).
+combine('drawBeforeUsage1', [true, false]).
+combine('visibility0', ['compute', 'fragment']).
+filter((t) => {
+ // The buffer with `indirect` or `indexedIndirect` usage is always accessible in the draw
+ // call.
+ if (
+ (t.usage0 === 'indirect' || t.usage0 === 'indexedIndirect') && (
+ !t.usage0AccessibleInDraw || t.visibility0 !== 'fragment' || !t.drawBeforeUsage1))
+ {
+ return false;
+ }
+ // The buffer usages `vertex` and `index` do nothing with shader visibilities.
+ if ((t.usage0 === 'vertex' || t.usage0 === 'index') && t.visibility0 !== 'fragment') {
+ return false;
+ }
+
+ // As usage0 is accessible in the draw call, visibility0 can only be 'fragment'.
+ if (t.usage0AccessibleInDraw && t.visibility0 !== 'fragment') {
+ return false;
+ }
+ // As usage1 is accessible in the draw call, the draw call cannot be before usage1.
+ if (t.drawBeforeUsage1 && t.usage1AccessibleInDraw) {
+ return false;
+ }
+
+ // Avoid writable storage buffer bindings aliasing.
+ if (t.usage0 === 'storage' && t.usage1 === 'storage') {
+ return false;
+ }
+ return true;
+}).
+combine('visibility1', ['compute', 'fragment']).
+filter((t) => {
+ if (
+ (t.usage1 === 'indirect' || t.usage1 === 'indexedIndirect') && (
+ !t.usage1AccessibleInDraw || t.visibility1 !== 'fragment' || t.drawBeforeUsage1))
+ {
+ return false;
+ }
+ if ((t.usage1 === 'vertex' || t.usage1 === 'index') && t.visibility1 !== 'fragment') {
+ return false;
+ }
+ // When the first buffer usage is `indirect` or `indexedIndirect`, there has already been
+ // one draw call, so in this test we always make the second usage inaccessible in the draw
+ // call.
+ if (
+ t.usage1AccessibleInDraw && (
+ t.visibility1 !== 'fragment' ||
+ t.usage0 === 'indirect' ||
+ t.usage0 === 'indexedIndirect'))
+ {
+ return false;
+ }
+ // When the first buffer usage is `index` and is accessible in the draw call, the second
+ // usage cannot be `indirect` (it should be `indexedIndirect` for the tests on indirect draw
+ // calls)
+ if (t.usage0 === 'index' && t.usage0AccessibleInDraw && t.usage1 === 'indirect') {
+ return false;
+ }
+ return true;
+}).
+combine('hasOverlap', [true, false])
+).
+fn((t) => {
+ const {
+ // Buffer with usage0 will be "used" in the draw call if this value is true.
+ usage0AccessibleInDraw,
+ // Buffer with usage1 will be "used" in the draw call if this value is true.
+ usage1AccessibleInDraw,
+ // Whether we will have the draw call before setting the buffer usage as "usage1" or not.
+ // If it is true: set-usage0 -> draw -> set-usage1 or indirect-draw -> set-usage1
+ // Otherwise: set-usage0 -> set-usage1 -> draw or set-usage0 -> indirect-draw
+ drawBeforeUsage1,
+ usage0,
+ visibility0,
+ usage1,
+ visibility1,
+ hasOverlap
+ } = t.params;
+ const buffer = t.createBufferWithState('valid', {
+ size: kBoundBufferSize * 2,
+ usage:
+ GPUBufferUsage.UNIFORM |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX |
+ GPUBufferUsage.INDIRECT
+ });
+
+ const UseBufferOnRenderPassEncoder = (
+ bufferAccessibleInDraw,
+ bufferIndex,
+ offset,
+ usage,
+ bindGroupVisibility,
+ renderPassEncoder,
+ usedBindGroupLayouts) =>
+ {
+ switch (usage) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup = t.createBindGroupForTest(buffer, offset, usage, bindGroupVisibility);
+ renderPassEncoder.setBindGroup(bufferIndex, bindGroup);
+ // To "use" the bind group we will set the corresponding bind group layout in the
+ // pipeline layout when creating the render pipeline.
+ if (bufferAccessibleInDraw && bindGroupVisibility === 'fragment') {
+ usedBindGroupLayouts.push(t.createBindGroupLayoutForTest(usage, bindGroupVisibility));
+ }
+ break;
+ }
+ case 'vertex':{
+ renderPassEncoder.setVertexBuffer(bufferIndex, buffer, offset);
+ break;
+ }
+ case 'index':{
+ renderPassEncoder.setIndexBuffer(buffer, 'uint16', offset);
+ break;
+ }
+ case 'indirect':
+ case 'indexedIndirect':{
+ // We will handle the indirect draw calls later.
+ break;
+ }
+ }
+ };
+
+ const MakeDrawCallWithOneUsage = (
+ usage,
+ offset,
+ renderPassEncoder) =>
+ {
+ switch (usage) {
+ case 'uniform':
+ case 'read-only-storage':
+ case 'storage':
+ case 'vertex':
+ renderPassEncoder.draw(1);
+ break;
+ case 'index':
+ renderPassEncoder.drawIndexed(1);
+ break;
+ case 'indirect':
+ renderPassEncoder.drawIndirect(buffer, offset);
+ break;
+ case 'indexedIndirect':{
+ const indexBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ });
+ renderPassEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ renderPassEncoder.drawIndexedIndirect(buffer, offset);
+ break;
+ }
+ }
+ };
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+
+ // Set buffer with usage0
+ const offset0 = 0;
+ // Invisible bind groups or vertex buffers are all bound to the slot 1.
+ const bufferIndex0 = visibility0 === 'fragment' ? 0 : 1;
+ const usedBindGroupLayouts = [];
+
+ UseBufferOnRenderPassEncoder(
+ usage0AccessibleInDraw,
+ bufferIndex0,
+ offset0,
+ usage0,
+ visibility0,
+ renderPassEncoder,
+ usedBindGroupLayouts
+ );
+
+ let vertexBufferCount = 0;
+
+ // Set pipeline and do draw call if drawBeforeUsage1 === true
+ if (drawBeforeUsage1) {
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: usedBindGroupLayouts
+ });
+ // To "use" the vertex buffer we need to set the corresponding vertex buffer layout when
+ // creating the render pipeline.
+ if (usage0 === 'vertex' && usage0AccessibleInDraw) {
+ ++vertexBufferCount;
+ }
+ const pipeline = t.createRenderPipelineForTest(pipelineLayout, vertexBufferCount);
+ renderPassEncoder.setPipeline(pipeline);
+ if (!usage0AccessibleInDraw) {
+ renderPassEncoder.draw(1);
+ } else {
+ MakeDrawCallWithOneUsage(usage0, offset0, renderPassEncoder);
+ }
+ }
+
+ // Set buffer with usage1.
+ const offset1 = hasOverlap ? offset0 : kBoundBufferSize;
+ let bufferIndex1 = 0;
+ if (visibility1 !== 'fragment') {
+ // Invisible bind groups or vertex buffers are all bound to the slot 1.
+ bufferIndex1 = 1;
+ } else if (visibility0 === 'fragment' && usage0AccessibleInDraw) {
+ // When buffer is bound to different bind groups or bound as vertex buffers in one render pass
+ // encoder, the second buffer binding should consume the slot 1.
+ if (IsBufferUsageInBindGroup(usage0) && IsBufferUsageInBindGroup(usage1)) {
+ bufferIndex1 = 1;
+ } else if (usage0 === 'vertex' && usage1 === 'vertex') {
+ bufferIndex1 = 1;
+ }
+ }
+
+ UseBufferOnRenderPassEncoder(
+ usage1AccessibleInDraw,
+ bufferIndex1,
+ offset1,
+ usage1,
+ visibility1,
+ renderPassEncoder,
+ usedBindGroupLayouts
+ );
+
+ // Set pipeline and do draw call if drawBeforeUsage1 === false
+ if (!drawBeforeUsage1) {
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: usedBindGroupLayouts
+ });
+ if (usage1 === 'vertex' && usage1AccessibleInDraw) {
+ // To "use" the vertex buffer we need to set the corresponding vertex buffer layout when
+ // creating the render pipeline.
+ ++vertexBufferCount;
+ }
+ const pipeline = t.createRenderPipelineForTest(pipelineLayout, vertexBufferCount);
+ renderPassEncoder.setPipeline(pipeline);
+
+ assert(usage0 !== 'indirect');
+ if (!usage0AccessibleInDraw && !usage1AccessibleInDraw) {
+ renderPassEncoder.draw(1);
+ } else if (usage0AccessibleInDraw && !usage1AccessibleInDraw) {
+ MakeDrawCallWithOneUsage(usage0, offset0, renderPassEncoder);
+ } else if (!usage0AccessibleInDraw && usage1AccessibleInDraw) {
+ MakeDrawCallWithOneUsage(usage1, offset1, renderPassEncoder);
+ } else {
+ if (usage1 === 'indexedIndirect') {
+ // If the index buffer has already been set (as usage0), we won't need to set another
+ // index buffer.
+ if (usage0 !== 'index') {
+ const indexBuffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ });
+ renderPassEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ }
+ renderPassEncoder.drawIndexedIndirect(buffer, offset1);
+ } else if (usage1 === 'indirect') {
+ assert(usage0 !== 'index');
+ renderPassEncoder.drawIndirect(buffer, offset1);
+ } else if (usage0 === 'index' || usage1 === 'index') {
+ // We need to call drawIndexed to "use" the index buffer (as usage0 or usage1).
+ renderPassEncoder.drawIndexed(1);
+ } else {
+ renderPassEncoder.draw(1);
+ }
+ }
+ }
+ renderPassEncoder.end();
+
+ const fail = usage0 === 'storage' !== (usage1 === 'storage');
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, fail);
+});
+
+g.test('subresources,buffer_usage_in_one_render_pass_with_two_draws').
+desc(
+ `
+Test that when one buffer is used in different draw calls in one render pass, its list of internal
+usages within one usage scope (all the commands in the whole render pass) can only be a compatible
+usage list, and the usage scope rules are not related to the buffer offset, while the draw calls in
+different render pass encoders belong to different usage scopes.`
+).
+params((u) =>
+u.
+combine('usage0', kAllBufferUsages).
+combine('usage1', kAllBufferUsages).
+beginSubcases().
+combine('inSamePass', [true, false]).
+combine('hasOverlap', [true, false])
+).
+fn((t) => {
+ const { usage0, usage1, inSamePass, hasOverlap } = t.params;
+ const buffer = t.createBufferWithState('valid', {
+ size: kBoundBufferSize * 2,
+ usage:
+ GPUBufferUsage.UNIFORM |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX |
+ GPUBufferUsage.INDIRECT
+ });
+ const UseBufferOnRenderPassEncoderInDrawCall = (
+ offset,
+ usage,
+ renderPassEncoder) =>
+ {
+ switch (usage) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroupLayout = t.createBindGroupLayoutForTest(usage, 'fragment');
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ });
+ const pipeline = t.createRenderPipelineForTest(pipelineLayout, 0);
+ renderPassEncoder.setPipeline(pipeline);
+ const bindGroup = t.createBindGroupForTest(buffer, offset, usage, 'fragment');
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(1);
+ break;
+ }
+ case 'vertex':{
+ const kVertexBufferCount = 1;
+ const pipeline = t.createRenderPipelineForTest('auto', kVertexBufferCount);
+ renderPassEncoder.setPipeline(pipeline);
+ renderPassEncoder.setVertexBuffer(0, buffer, offset);
+ renderPassEncoder.draw(1);
+ break;
+ }
+ case 'index':{
+ const pipeline = t.createRenderPipelineForTest('auto', 0);
+ renderPassEncoder.setPipeline(pipeline);
+ renderPassEncoder.setIndexBuffer(buffer, 'uint16', offset);
+ renderPassEncoder.drawIndexed(1);
+ break;
+ }
+ case 'indirect':{
+ const pipeline = t.createRenderPipelineForTest('auto', 0);
+ renderPassEncoder.setPipeline(pipeline);
+ renderPassEncoder.drawIndirect(buffer, offset);
+ break;
+ }
+ case 'indexedIndirect':{
+ const pipeline = t.createRenderPipelineForTest('auto', 0);
+ renderPassEncoder.setPipeline(pipeline);
+ const indexBuffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ });
+ renderPassEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ renderPassEncoder.drawIndexedIndirect(buffer, offset);
+ break;
+ }
+ }
+ };
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+
+ const offset0 = 0;
+ UseBufferOnRenderPassEncoderInDrawCall(offset0, usage0, renderPassEncoder);
+
+ const offset1 = hasOverlap ? offset0 : kBoundBufferSize;
+ if (inSamePass) {
+ UseBufferOnRenderPassEncoderInDrawCall(offset1, usage1, renderPassEncoder);
+ renderPassEncoder.end();
+ } else {
+ renderPassEncoder.end();
+ const anotherRenderPassEncoder = t.beginSimpleRenderPass(encoder);
+ UseBufferOnRenderPassEncoderInDrawCall(offset1, usage1, anotherRenderPassEncoder);
+ anotherRenderPassEncoder.end();
+ }
+
+ const fail = inSamePass && usage0 === 'storage' !== (usage1 === 'storage');
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, fail);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.js
new file mode 100644
index 0000000000..189c3be230
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/buffer/in_pass_misc.spec.js
@@ -0,0 +1,409 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test other buffer usage validation rules that are not tests in ./in_pass_encoder.spec.js.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { unreachable } from '../../../../../common/util/util.js';
+
+import { BufferResourceUsageTest, kAllBufferUsages } from './in_pass_encoder.spec.js';
+
+export const g = makeTestGroup(BufferResourceUsageTest);
+
+const kBufferSize = 256;
+
+g.test('subresources,reset_buffer_usage_before_dispatch').
+desc(
+ `
+Test that the buffer usages which are reset by another state-setting commands before a dispatch call
+do not contribute directly to any usage scope in a compute pass.`
+).
+params((u) =>
+u.
+combine('usage0', ['uniform', 'storage', 'read-only-storage']).
+combine('usage1', ['uniform', 'storage', 'read-only-storage', 'indirect'])
+).
+fn((t) => {
+ const { usage0, usage1 } = t.params;
+
+ const kUsages = GPUBufferUsage.UNIFORM | GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT;
+ const buffer = t.createBufferWithState('valid', {
+ size: kBufferSize,
+ usage: kUsages
+ });
+ const anotherBuffer = t.createBufferWithState('valid', {
+ size: kBufferSize,
+ usage: kUsages
+ });
+
+ const bindGroupLayouts = [
+ t.createBindGroupLayoutForTest(usage0, 'compute')];
+
+ if (usage1 !== 'indirect') {
+ bindGroupLayouts.push(t.createBindGroupLayoutForTest(usage1, 'compute'));
+ }
+ const pipelineLayout = t.device.createPipelineLayout({ bindGroupLayouts });
+ const computePipeline = t.createNoOpComputePipeline(pipelineLayout);
+
+ const encoder = t.device.createCommandEncoder();
+ const computePassEncoder = encoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+
+ // Set usage0 for buffer at bind group index 0
+ const bindGroup0 = t.createBindGroupForTest(buffer, 0, usage0, 'compute');
+ computePassEncoder.setBindGroup(0, bindGroup0);
+
+ // Reset bind group index 0 with another bind group that uses anotherBuffer
+ const anotherBindGroup = t.createBindGroupForTest(anotherBuffer, 0, usage0, 'compute');
+ computePassEncoder.setBindGroup(0, anotherBindGroup);
+
+ // Set usage1 for buffer
+ switch (usage1) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup1 = t.createBindGroupForTest(buffer, 0, usage1, 'compute');
+ computePassEncoder.setBindGroup(1, bindGroup1);
+ computePassEncoder.dispatchWorkgroups(1);
+ break;
+ }
+ case 'indirect':{
+ computePassEncoder.dispatchWorkgroupsIndirect(buffer, 0);
+ break;
+ }
+ }
+ computePassEncoder.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, false);
+});
+
+g.test('subresources,reset_buffer_usage_before_draw').
+desc(
+ `
+Test that the buffer usages which are reset by another state-setting commands before a draw call
+still contribute directly to the usage scope of the draw call.`
+).
+params((u) =>
+u.
+combine('usage0', ['uniform', 'storage', 'read-only-storage', 'vertex', 'index']).
+combine('usage1', kAllBufferUsages).
+unless((t) => {
+ return t.usage0 === 'index' && t.usage1 === 'indirect';
+})
+).
+fn((t) => {
+ const { usage0, usage1 } = t.params;
+
+ const kUsages =
+ GPUBufferUsage.UNIFORM |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.INDIRECT |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX;
+ const buffer = t.createBufferWithState('valid', {
+ size: kBufferSize,
+ usage: kUsages
+ });
+ const anotherBuffer = t.createBufferWithState('valid', {
+ size: kBufferSize,
+ usage: kUsages
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+
+ const bindGroupLayouts = [];
+ let vertexBufferCount = 0;
+
+ // Set buffer as usage0 and reset buffer with anotherBuffer as usage0
+ switch (usage0) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup0 = t.createBindGroupForTest(buffer, 0, usage0, 'fragment');
+ renderPassEncoder.setBindGroup(bindGroupLayouts.length, bindGroup0);
+
+ const anotherBindGroup = t.createBindGroupForTest(anotherBuffer, 0, usage0, 'fragment');
+ renderPassEncoder.setBindGroup(bindGroupLayouts.length, anotherBindGroup);
+
+ bindGroupLayouts.push(t.createBindGroupLayoutForTest(usage0, 'fragment'));
+ break;
+ }
+ case 'vertex':{
+ renderPassEncoder.setVertexBuffer(vertexBufferCount, buffer);
+ renderPassEncoder.setVertexBuffer(vertexBufferCount, anotherBuffer);
+
+ ++vertexBufferCount;
+ break;
+ }
+ case 'index':{
+ renderPassEncoder.setIndexBuffer(buffer, 'uint16');
+ renderPassEncoder.setIndexBuffer(anotherBuffer, 'uint16');
+ break;
+ }
+ }
+
+ // Set buffer as usage1
+ switch (usage1) {
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup1 = t.createBindGroupForTest(buffer, 0, usage1, 'fragment');
+ renderPassEncoder.setBindGroup(bindGroupLayouts.length, bindGroup1);
+
+ bindGroupLayouts.push(t.createBindGroupLayoutForTest(usage1, 'fragment'));
+ break;
+ }
+ case 'vertex':{
+ renderPassEncoder.setVertexBuffer(vertexBufferCount, buffer);
+ ++vertexBufferCount;
+ break;
+ }
+ case 'index':{
+ renderPassEncoder.setIndexBuffer(buffer, 'uint16');
+ break;
+ }
+ case 'indirect':
+ case 'indexedIndirect':
+ break;
+ }
+
+ // Add draw call
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts
+ });
+ const renderPipeline = t.createRenderPipelineForTest(pipelineLayout, vertexBufferCount);
+ renderPassEncoder.setPipeline(renderPipeline);
+ switch (usage1) {
+ case 'indexedIndirect':{
+ if (usage0 !== 'index') {
+ const indexBuffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ });
+ renderPassEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ }
+ renderPassEncoder.drawIndexedIndirect(buffer, 0);
+ break;
+ }
+ case 'indirect':{
+ renderPassEncoder.drawIndirect(buffer, 0);
+ break;
+ }
+ case 'index':{
+ renderPassEncoder.drawIndexed(1);
+ break;
+ }
+ case 'vertex':
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ if (usage0 === 'index') {
+ renderPassEncoder.drawIndexed(1);
+ } else {
+ renderPassEncoder.draw(1);
+ }
+ break;
+ }
+ }
+
+ renderPassEncoder.end();
+
+ const fail = usage0 === 'storage' !== (usage1 === 'storage');
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, fail);
+});
+
+g.test('subresources,buffer_usages_in_copy_and_pass').
+desc(
+ `
+ Test that using one buffer in a copy command, a render or compute pass encoder is always allowed
+ as WebGPU SPEC (chapter 3.4.5) defines that out of any pass encoder, each command belongs to one
+ separated usage scope.`
+).
+params((u) =>
+u.
+combine('usage0', [
+'copy-src',
+'copy-dst',
+'uniform',
+'storage',
+'read-only-storage',
+'vertex',
+'index',
+'indirect',
+'indexedIndirect']
+).
+combine('usage1', [
+'copy-src',
+'copy-dst',
+'uniform',
+'storage',
+'read-only-storage',
+'vertex',
+'index',
+'indirect',
+'indexedIndirect']
+).
+combine('pass', ['render', 'compute']).
+unless(({ usage0, usage1, pass }) => {
+ const IsCopy = (usage) => {
+ return usage === 'copy-src' || usage === 'copy-dst';
+ };
+ // We intend to test copy usages in this test.
+ if (!IsCopy(usage0) && !IsCopy(usage1)) {
+ return true;
+ }
+ // When both usage0 and usage1 are copy usages, 'pass' is meaningless so in such situation
+ // we just need to reserve one value as 'pass'.
+ if (IsCopy(usage0) && IsCopy(usage1)) {
+ return pass === 'compute';
+ }
+
+ const IsValidComputeUsage = (usage) => {
+ switch (usage) {
+ case 'vertex':
+ case 'index':
+ case 'indexedIndirect':
+ return false;
+ default:
+ return true;
+ }
+ };
+ if (pass === 'compute') {
+ return !IsValidComputeUsage(usage0) || !IsValidComputeUsage(usage1);
+ }
+
+ return false;
+})
+).
+fn((t) => {
+ const { usage0, usage1, pass } = t.params;
+
+ const kUsages =
+ GPUBufferUsage.COPY_SRC |
+ GPUBufferUsage.COPY_DST |
+ GPUBufferUsage.UNIFORM |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.INDIRECT |
+ GPUBufferUsage.VERTEX |
+ GPUBufferUsage.INDEX;
+ const buffer = t.createBufferWithState('valid', {
+ size: kBufferSize,
+ usage: kUsages
+ });
+
+ const UseBufferOnCommandEncoder = (
+ usage,
+
+
+
+
+
+
+
+
+
+ encoder) =>
+ {
+ switch (usage) {
+ case 'copy-src':{
+ const destinationBuffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ encoder.copyBufferToBuffer(buffer, 0, destinationBuffer, 0, 4);
+ break;
+ }
+ case 'copy-dst':{
+ const sourceBuffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ encoder.copyBufferToBuffer(sourceBuffer, 0, buffer, 0, 4);
+ break;
+ }
+ case 'uniform':
+ case 'storage':
+ case 'read-only-storage':{
+ const bindGroup = t.createBindGroupForTest(buffer, 0, usage, 'fragment');
+ switch (pass) {
+ case 'render':{
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'compute':{
+ const computePassEncoder = encoder.beginComputePass();
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.end();
+ break;
+ }
+ default:
+ unreachable();
+ }
+ break;
+ }
+ case 'vertex':{
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+ renderPassEncoder.setVertexBuffer(0, buffer);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'index':{
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+ renderPassEncoder.setIndexBuffer(buffer, 'uint16');
+ renderPassEncoder.end();
+ break;
+ }
+ case 'indirect':{
+ switch (pass) {
+ case 'render':{
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+ const renderPipeline = t.createNoOpRenderPipeline();
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.drawIndirect(buffer, 0);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'compute':{
+ const computePassEncoder = encoder.beginComputePass();
+ const computePipeline = t.createNoOpComputePipeline();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroupsIndirect(buffer, 0);
+ computePassEncoder.end();
+ break;
+ }
+ default:
+ unreachable();
+ }
+ break;
+ }
+ case 'indexedIndirect':{
+ const renderPassEncoder = t.beginSimpleRenderPass(encoder);
+ const renderPipeline = t.createNoOpRenderPipeline();
+ renderPassEncoder.setPipeline(renderPipeline);
+ const indexBuffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.INDEX
+ });
+ renderPassEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ renderPassEncoder.drawIndexedIndirect(buffer, 0);
+ renderPassEncoder.end();
+ break;
+ }
+ default:
+ unreachable();
+ }
+ };
+
+ const encoder = t.device.createCommandEncoder();
+ UseBufferOnCommandEncoder(usage0, encoder);
+ UseBufferOnCommandEncoder(usage1, encoder);
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, false);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.js
new file mode 100644
index 0000000000..f354053e4f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.js
@@ -0,0 +1,1395 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Texture Usages Validation Tests in Render Pass and Compute Pass.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { pp } from '../../../../../common/util/preprocessor.js';
+import { assert } from '../../../../../common/util/util.js';
+import { GPUConst } from '../../../../constants.js';
+import {
+ kDepthStencilFormats,
+ kDepthStencilFormatResolvedAspect,
+ kTextureFormatInfo } from
+'../../../../format_info.js';
+import { ValidationTest } from '../../validation_test.js';
+
+
+const kTextureBindingTypes = [
+'sampled-texture',
+'multisampled-texture',
+'writeonly-storage-texture'];
+
+
+const SIZE = 32;
+class TextureUsageTracking extends ValidationTest {
+ createTexture(
+ options =
+
+
+
+
+
+
+
+ {})
+ {
+ const {
+ width = SIZE,
+ height = SIZE,
+ arrayLayerCount = 1,
+ mipLevelCount = 1,
+ sampleCount = 1,
+ format = 'rgba8unorm',
+ usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
+ } = options;
+
+ return this.device.createTexture({
+ size: { width, height, depthOrArrayLayers: arrayLayerCount },
+ mipLevelCount,
+ sampleCount,
+ dimension: '2d',
+ format,
+ usage
+ });
+ }
+
+ createBindGroupLayout(
+ binding,
+ bindingType,
+ viewDimension,
+ options =
+
+
+ {})
+ {
+ const { sampleType, format } = options;
+ let entry;
+ switch (bindingType) {
+ case 'sampled-texture':
+ entry = { texture: { viewDimension, sampleType } };
+ break;
+ case 'multisampled-texture':
+ entry = { texture: { viewDimension, multisampled: true, sampleType } };
+ break;
+ case 'writeonly-storage-texture':
+ assert(format !== undefined);
+ entry = { storageTexture: { access: 'write-only', format, viewDimension } };
+ break;
+ }
+
+ return this.device.createBindGroupLayout({
+ entries: [
+ { binding, visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT, ...entry }]
+
+ });
+ }
+
+ createBindGroup(
+ binding,
+ resource,
+ bindingType,
+ viewDimension,
+ options =
+
+
+ {})
+ {
+ return this.device.createBindGroup({
+ entries: [{ binding, resource }],
+ layout: this.createBindGroupLayout(binding, bindingType, viewDimension, options)
+ });
+ }
+
+ createAndExecuteBundle(
+ binding,
+ bindGroup,
+ pass,
+ depthStencilFormat)
+ {
+ const bundleEncoder = this.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm'],
+ depthStencilFormat
+ });
+ bundleEncoder.setBindGroup(binding, bindGroup);
+ const bundle = bundleEncoder.finish();
+ pass.executeBundles([bundle]);
+ }
+
+ beginSimpleRenderPass(encoder, view) {
+ return encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view,
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ }
+
+ /**
+ * Create two bind groups. Resource usages conflict between these two bind groups. But resource
+ * usage inside each bind group doesn't conflict.
+ */
+ makeConflictingBindGroups() {
+ 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' })];
+
+ return {
+ bindGroupLayouts,
+ bindGroups: [
+ this.device.createBindGroup({
+ layout: bindGroupLayouts[0],
+ entries: [{ binding: 0, resource: view }]
+ }),
+ this.device.createBindGroup({
+ layout: bindGroupLayouts[1],
+ entries: [{ binding: 0, resource: view }]
+ })]
+
+ };
+ }
+
+ testValidationScope(compute)
+
+
+
+
+
+ {
+ const { bindGroupLayouts, bindGroups } = this.makeConflictingBindGroups();
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = compute ?
+ encoder.beginComputePass() :
+ this.beginSimpleRenderPass(encoder, this.createTexture().createView());
+
+ // Create pipeline. Note that bindings unused in pipeline should be validated too.
+ const pipelineLayout = this.device.createPipelineLayout({
+ bindGroupLayouts
+ });
+ const pipeline = compute ?
+ this.createNoOpComputePipeline(pipelineLayout) :
+ this.createNoOpRenderPipeline(pipelineLayout);
+ return {
+ bindGroup0: bindGroups[0],
+ bindGroup1: bindGroups[1],
+ encoder,
+ pass,
+ pipeline
+ };
+ }
+
+ setPipeline(
+ pass,
+ pipeline)
+ {
+ if (pass instanceof GPUComputePassEncoder) {
+ pass.setPipeline(pipeline);
+ } else {
+ pass.setPipeline(pipeline);
+ }
+ }
+
+ issueDrawOrDispatch(pass) {
+ if (pass instanceof GPUComputePassEncoder) {
+ pass.dispatchWorkgroups(1);
+ } else {
+ pass.draw(3, 1, 0, 0);
+ }
+ }
+
+ setComputePipelineAndCallDispatch(pass, layout) {
+ const pipeline = this.createNoOpComputePipeline(layout);
+ pass.setPipeline(pipeline);
+ pass.dispatchWorkgroups(1);
+ }
+}
+
+export const g = makeTestGroup(TextureUsageTracking);
+
+const BASE_LEVEL = 1;
+const TOTAL_LEVELS = 6;
+const BASE_LAYER = 1;
+const TOTAL_LAYERS = 6;
+const SLICE_COUNT = 2;
+
+g.test('subresources_and_binding_types_combination_for_color').
+desc(
+ `
+ Test the resource usage rules by using two views of the same GPUTexture in a usage scope. Tests
+ various combinations of {sampled, storage, render target} usages, mip-level ranges, and
+ array-layer ranges, in {compute pass, render pass, render pass via bundle}.
+ - Error if a subresource (level/layer) is used as read+write or write+write in the scope,
+ except when both usages are writeonly-storage-texture which is allowed.
+ `
+).
+params((u) =>
+u.
+combine('compute', [false, true]).
+expandWithParams(
+ (p) =>
+ [
+ { _usageOK: true, type0: 'sampled-texture', type1: 'sampled-texture' },
+ { _usageOK: false, type0: 'sampled-texture', type1: 'writeonly-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.
+ {
+ _usageOK: !p.compute,
+ type0: 'writeonly-storage-texture',
+ type1: 'writeonly-storage-texture'
+ },
+ { _usageOK: false, type0: 'writeonly-storage-texture', type1: 'render-target' },
+ { _usageOK: false, type0: 'render-target', type1: 'render-target' }]
+
+).
+beginSubcases().
+combine('binding0InBundle', [false, true]).
+combine('binding1InBundle', [false, true]).
+unless(
+ (p) =>
+ // We can't set 'render-target' in bundle, so we need to exclude it from bundle.
+ p.binding0InBundle && p.type0 === 'render-target' ||
+ p.binding1InBundle && p.type1 === 'render-target' ||
+ // We can't set 'render-target' or bundle in compute.
+ p.compute && (
+ p.binding0InBundle ||
+ p.binding1InBundle ||
+ p.type0 === 'render-target' ||
+ p.type1 === 'render-target')
+).
+combineWithParams([
+// Two texture usages are binding to the same texture subresource.
+{
+ levelCount0: 1,
+ layerCount0: 1,
+ baseLevel1: BASE_LEVEL,
+ levelCount1: 1,
+ baseLayer1: BASE_LAYER,
+ layerCount1: 1,
+ _resourceSuccess: false
+},
+
+// Two texture usages are binding to different mip levels of the same texture.
+{
+ levelCount0: 1,
+ layerCount0: 1,
+ baseLevel1: BASE_LEVEL + 1,
+ levelCount1: 1,
+ baseLayer1: BASE_LAYER,
+ layerCount1: 1,
+ _resourceSuccess: true
+},
+
+// Two texture usages are binding to different array layers of the same texture.
+{
+ levelCount0: 1,
+ layerCount0: 1,
+ baseLevel1: BASE_LEVEL,
+ levelCount1: 1,
+ baseLayer1: BASE_LAYER + 1,
+ layerCount1: 1,
+ _resourceSuccess: true
+},
+
+// The second texture usage contains the whole mip chain where the first texture usage is
+// using.
+{
+ levelCount0: 1,
+ layerCount0: 1,
+ baseLevel1: 0,
+ levelCount1: TOTAL_LEVELS,
+ baseLayer1: BASE_LAYER,
+ layerCount1: 1,
+ _resourceSuccess: false
+},
+
+// The second texture usage contains all layers where the first texture usage is using.
+{
+ levelCount0: 1,
+ layerCount0: 1,
+ baseLevel1: BASE_LEVEL,
+ levelCount1: 1,
+ baseLayer1: 0,
+ layerCount1: TOTAL_LAYERS,
+ _resourceSuccess: false
+},
+
+// The second texture usage contains all subresources where the first texture usage is
+// using.
+{
+ levelCount0: 1,
+ layerCount0: 1,
+ baseLevel1: 0,
+ levelCount1: TOTAL_LEVELS,
+ baseLayer1: 0,
+ layerCount1: TOTAL_LAYERS,
+ _resourceSuccess: false
+},
+
+// Both of the two usages access a few mip levels on the same layer but they don't overlap.
+{
+ levelCount0: SLICE_COUNT,
+ layerCount0: 1,
+ baseLevel1: BASE_LEVEL + SLICE_COUNT,
+ levelCount1: 3,
+ baseLayer1: BASE_LAYER,
+ layerCount1: 1,
+ _resourceSuccess: true
+},
+
+// Both of the two usages access a few mip levels on the same layer and they overlap.
+{
+ levelCount0: SLICE_COUNT,
+ layerCount0: 1,
+ baseLevel1: BASE_LEVEL + SLICE_COUNT - 1,
+ levelCount1: 3,
+ baseLayer1: BASE_LAYER,
+ layerCount1: 1,
+ _resourceSuccess: false
+},
+
+// Both of the two usages access a few array layers on the same level but they don't
+// overlap.
+{
+ levelCount0: 1,
+ layerCount0: SLICE_COUNT,
+ baseLevel1: BASE_LEVEL,
+ levelCount1: 1,
+ baseLayer1: BASE_LAYER + SLICE_COUNT,
+ layerCount1: 3,
+ _resourceSuccess: true
+},
+
+// Both of the two usages access a few array layers on the same level and they overlap.
+{
+ levelCount0: 1,
+ layerCount0: SLICE_COUNT,
+ baseLevel1: BASE_LEVEL,
+ levelCount1: 1,
+ baseLayer1: BASE_LAYER + SLICE_COUNT - 1,
+ layerCount1: 3,
+ _resourceSuccess: false
+},
+
+// Both of the two usages access a few array layers and mip levels but they don't overlap.
+{
+ levelCount0: SLICE_COUNT,
+ layerCount0: SLICE_COUNT,
+ baseLevel1: BASE_LEVEL + SLICE_COUNT,
+ levelCount1: 3,
+ baseLayer1: BASE_LAYER + SLICE_COUNT,
+ layerCount1: 3,
+ _resourceSuccess: true
+},
+
+// Both of the two usages access a few array layers and mip levels and they overlap.
+{
+ levelCount0: SLICE_COUNT,
+ layerCount0: SLICE_COUNT,
+ baseLevel1: BASE_LEVEL + SLICE_COUNT - 1,
+ levelCount1: 3,
+ baseLayer1: BASE_LAYER + SLICE_COUNT - 1,
+ layerCount1: 3,
+ _resourceSuccess: false
+}]
+).
+unless(
+ (p) =>
+ // Every color attachment or storage texture can use only one single subresource.
+ p.type0 !== 'sampled-texture' && (p.levelCount0 !== 1 || p.layerCount0 !== 1) ||
+ p.type1 !== 'sampled-texture' && (p.levelCount1 !== 1 || p.layerCount1 !== 1) ||
+ // All color attachments' size should be the same.
+ p.type0 === 'render-target' &&
+ p.type1 === 'render-target' &&
+ p.baseLevel1 !== BASE_LEVEL
+)
+).
+fn((t) => {
+ const {
+ compute,
+ binding0InBundle,
+ binding1InBundle,
+ levelCount0,
+ layerCount0,
+ baseLevel1,
+ baseLayer1,
+ levelCount1,
+ layerCount1,
+ type0,
+ type1,
+ _usageOK,
+ _resourceSuccess
+ } = t.params;
+
+ const texture = t.createTexture({
+ arrayLayerCount: TOTAL_LAYERS,
+ mipLevelCount: TOTAL_LEVELS,
+ usage:
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.STORAGE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const dimension0 = layerCount0 !== 1 ? '2d-array' : '2d';
+ const view0 = texture.createView({
+ dimension: dimension0,
+ baseMipLevel: BASE_LEVEL,
+ mipLevelCount: levelCount0,
+ baseArrayLayer: BASE_LAYER,
+ arrayLayerCount: layerCount0
+ });
+
+ const dimension1 = layerCount1 !== 1 ? '2d-array' : '2d';
+ const view1 = texture.createView({
+ dimension: dimension1,
+ baseMipLevel: baseLevel1,
+ mipLevelCount: levelCount1,
+ baseArrayLayer: baseLayer1,
+ arrayLayerCount: layerCount1
+ });
+
+ const viewsAreSame =
+ dimension0 === dimension1 &&
+ layerCount0 === layerCount1 &&
+ BASE_LEVEL === baseLevel1 &&
+ levelCount0 === levelCount1 &&
+ BASE_LAYER === baseLayer1 &&
+ layerCount0 === layerCount1;
+ if (!viewsAreSame && t.isCompatibility) {
+ t.skip('different views of same texture are not supported in compatibility mode');
+ }
+
+ const encoder = t.device.createCommandEncoder();
+ if (type0 === 'render-target') {
+ // Note that type1 is 'render-target' too. So we don't need to create bindings.
+ assert(type1 === 'render-target');
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: view0,
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ },
+ {
+ view: view1,
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ } else {
+ const pass = compute ?
+ encoder.beginComputePass() :
+ t.beginSimpleRenderPass(
+ encoder,
+ type1 === 'render-target' ? view1 : t.createTexture().createView()
+ );
+
+ const bgls = [];
+ // Create bind groups. Set bind groups in pass directly or set bind groups in bundle.
+ const storageTextureFormat0 = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+
+ const bgl0 = t.createBindGroupLayout(0, type0, dimension0, { format: storageTextureFormat0 });
+ const bindGroup0 = t.device.createBindGroup({
+ layout: bgl0,
+ entries: [{ binding: 0, resource: view0 }]
+ });
+ bgls.push(bgl0);
+
+ if (binding0InBundle) {
+ assert(pass instanceof GPURenderPassEncoder);
+ t.createAndExecuteBundle(0, bindGroup0, pass);
+ } else {
+ pass.setBindGroup(0, bindGroup0);
+ }
+ if (type1 !== 'render-target') {
+ const storageTextureFormat1 = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
+
+ const bgl1 = t.createBindGroupLayout(1, type1, dimension1, {
+ format: storageTextureFormat1
+ });
+ const bindGroup1 = t.device.createBindGroup({
+ layout: bgl1,
+ entries: [{ binding: 1, resource: view1 }]
+ });
+ bgls.push(bgl1);
+
+ if (binding1InBundle) {
+ assert(pass instanceof GPURenderPassEncoder);
+ t.createAndExecuteBundle(1, bindGroup1, pass);
+ } else {
+ pass.setBindGroup(1, bindGroup1);
+ }
+ }
+ if (compute) {
+ t.setComputePipelineAndCallDispatch(
+ pass,
+ t.device.createPipelineLayout({ bindGroupLayouts: bgls })
+ );
+ }
+ pass.end();
+ }
+
+ const success = _resourceSuccess || _usageOK;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('subresources_and_binding_types_combination_for_aspect').
+desc(
+ `
+ Test the resource usage rules by using two views of the same GPUTexture in a usage scope. Tests
+ various combinations of {sampled, render target} usages, {all, depth-only, stencil-only} aspects
+ that overlap a given subresources in {compute pass, render pass, render pass via bundle}.
+ - Error if a subresource (level/layer/aspect) is used as read+write or write+write in the
+ scope.
+ `
+).
+params((u) =>
+u.
+combine('compute', [false, true]).
+combine('binding0InBundle', [false, true]).
+combine('binding1InBundle', [false, true]).
+combine('format', kDepthStencilFormats).
+beginSubcases().
+combineWithParams([
+{
+ baseLevel: BASE_LEVEL,
+ baseLayer: BASE_LAYER,
+ _resourceSuccess: false
+},
+{
+ baseLevel: BASE_LEVEL + 1,
+ baseLayer: BASE_LAYER,
+ _resourceSuccess: true
+},
+{
+ baseLevel: BASE_LEVEL,
+ baseLayer: BASE_LAYER + 1,
+ _resourceSuccess: true
+}]
+).
+combine('aspect0', ['all', 'depth-only', 'stencil-only']).
+combine('aspect1', ['all', 'depth-only', 'stencil-only']).
+unless(
+ (p) =>
+ p.aspect0 === 'stencil-only' && !kTextureFormatInfo[p.format].stencil ||
+ p.aspect1 === 'stencil-only' && !kTextureFormatInfo[p.format].stencil
+).
+unless(
+ (p) =>
+ p.aspect0 === 'depth-only' && !kTextureFormatInfo[p.format].depth ||
+ p.aspect1 === 'depth-only' && !kTextureFormatInfo[p.format].depth
+).
+combineWithParams([
+{
+ type0: 'sampled-texture',
+ type1: 'sampled-texture',
+ _usageSuccess: true
+},
+{
+ type0: 'sampled-texture',
+ type1: 'render-target',
+ _usageSuccess: false
+}]
+).
+unless(
+ // Can't sample a multiplanar texture without selecting an aspect.
+ (p) =>
+ !!kTextureFormatInfo[p.format].depth &&
+ !!kTextureFormatInfo[p.format].stencil && (
+ p.aspect0 === 'all' && p.type0 === 'sampled-texture' ||
+ p.aspect1 === 'all' && p.type1 === 'sampled-texture')
+).
+unless(
+ (p) =>
+ // We can't set 'render-target' in bundle, so we need to exclude it from bundle.
+ p.binding1InBundle && p.type1 === 'render-target'
+).
+unless(
+ (p) =>
+ // We can't set 'render-target' or bundle in compute. Note that type0 is definitely not
+ // 'render-target'
+ p.compute && (p.binding0InBundle || p.binding1InBundle || p.type1 === 'render-target')
+).
+unless(
+ (p) =>
+ // Depth-stencil attachment views must encompass all aspects of the texture. Invalid
+ // cases are for depth-stencil textures when the aspect is not 'all'.
+ p.type1 === 'render-target' &&
+ !!kTextureFormatInfo[p.format].depth &&
+ !!kTextureFormatInfo[p.format].stencil &&
+ p.aspect1 !== 'all'
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn((t) => {
+ const {
+ compute,
+ binding0InBundle,
+ binding1InBundle,
+ format,
+ baseLevel,
+ baseLayer,
+ aspect0,
+ aspect1,
+ type0,
+ type1,
+ _resourceSuccess,
+ _usageSuccess
+ } = t.params;
+
+ const texture = t.createTexture({
+ arrayLayerCount: TOTAL_LAYERS,
+ mipLevelCount: TOTAL_LEVELS,
+ format
+ });
+
+ const view0 = texture.createView({
+ dimension: '2d',
+ baseMipLevel: BASE_LEVEL,
+ mipLevelCount: 1,
+ baseArrayLayer: BASE_LAYER,
+ arrayLayerCount: 1,
+ aspect: aspect0
+ });
+
+ const view1 = texture.createView({
+ dimension: '2d',
+ baseMipLevel: baseLevel,
+ mipLevelCount: 1,
+ baseArrayLayer: baseLayer,
+ arrayLayerCount: 1,
+ aspect: aspect1
+ });
+ const view1ResolvedFormat = kDepthStencilFormatResolvedAspect[format][aspect1];
+ const view1HasDepth = kTextureFormatInfo[view1ResolvedFormat].depth;
+ const view1HasStencil = kTextureFormatInfo[view1ResolvedFormat].stencil;
+
+ const encoder = t.device.createCommandEncoder();
+ // Color attachment's size should match depth/stencil attachment's size. Note that if
+ // type1 !== 'render-target' then there's no depthStencilAttachment to match anyway.
+ const depthStencilFormat = type1 === 'render-target' ? view1ResolvedFormat : undefined;
+
+ const size = SIZE >> baseLevel;
+ const pass = compute ?
+ encoder.beginComputePass() :
+ encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: t.createTexture({ width: size, height: size }).createView(),
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment: depthStencilFormat ?
+ {
+ view: view1,
+ depthStoreOp: view1HasDepth ? 'discard' : undefined,
+ depthLoadOp: view1HasDepth ? 'load' : undefined,
+ stencilStoreOp: view1HasStencil ? 'discard' : undefined,
+ stencilLoadOp: view1HasStencil ? 'load' : undefined
+ } :
+ undefined
+ });
+
+ const aspectSampleType = (format, aspect) => {
+ switch (aspect) {
+ case 'depth-only':
+ return 'depth';
+ case 'stencil-only':
+ return 'uint';
+ case 'all':
+ assert(kTextureFormatInfo[format].depth !== kTextureFormatInfo[format].stencil);
+ if (kTextureFormatInfo[format].stencil) {
+ return 'uint';
+ }
+ return 'depth';
+ }
+ };
+
+ // Create bind groups. Set bind groups in pass directly or set bind groups in bundle.
+ const bindGroup0 = t.createBindGroup(0, view0, type0, '2d', {
+ sampleType: type0 === 'sampled-texture' ? aspectSampleType(format, aspect0) : undefined
+ });
+ if (binding0InBundle) {
+ assert(pass instanceof GPURenderPassEncoder);
+ t.createAndExecuteBundle(0, bindGroup0, pass, depthStencilFormat);
+ } else {
+ pass.setBindGroup(0, bindGroup0);
+ }
+ if (type1 !== 'render-target') {
+ const bindGroup1 = t.createBindGroup(1, view1, type1, '2d', {
+ sampleType: type1 === 'sampled-texture' ? aspectSampleType(format, aspect1) : undefined
+ });
+ if (binding1InBundle) {
+ assert(pass instanceof GPURenderPassEncoder);
+ t.createAndExecuteBundle(1, bindGroup1, pass, depthStencilFormat);
+ } else {
+ pass.setBindGroup(1, bindGroup1);
+ }
+ }
+ if (compute) t.setComputePipelineAndCallDispatch(pass);
+ pass.end();
+
+ const disjointAspects =
+ aspect0 === 'depth-only' && aspect1 === 'stencil-only' ||
+ aspect0 === 'stencil-only' && aspect1 === 'depth-only';
+
+ // If subresources' mip/array slices has no overlap, or their binding types don't conflict,
+ // it will definitely success no matter what aspects they are binding to.
+ const success = disjointAspects || _resourceSuccess || _usageSuccess;
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('shader_stages_and_visibility,storage_write').
+desc(
+ `
+ Test that stage visibility doesn't affect resource usage validation.
+ - Use a texture as sampled, with 'readVisibility' {0,VERTEX,FRAGMENT,COMPUTE}
+ - Use a {same,different} texture as storage, with 'writeVisibility' {0,FRAGMENT,COMPUTE}
+
+ There should be a validation error IFF the same texture was used.
+ `
+).
+params((u) =>
+u.
+combine('compute', [false, true]).
+beginSubcases().
+combine('secondUseConflicts', [false, true]).
+combine('readVisibility', [
+0,
+GPUConst.ShaderStage.VERTEX,
+GPUConst.ShaderStage.FRAGMENT,
+GPUConst.ShaderStage.COMPUTE]
+).
+combine('writeVisibility', [0, GPUConst.ShaderStage.FRAGMENT, GPUConst.ShaderStage.COMPUTE])
+).
+fn((t) => {
+ const { compute, readVisibility, writeVisibility, secondUseConflicts } = t.params;
+
+ const usage = GPUTextureUsage.TEXTURE_BINDING | 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: {} },
+ {
+ binding: 1,
+ visibility: writeVisibility,
+ storageTexture: { access: 'write-only', format: 'rgba8unorm' }
+ }]
+
+ });
+ const bindGroup = t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ { binding: 0, resource: view },
+ { binding: 1, resource: view2 }]
+
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ if (compute) {
+ const pass = encoder.beginComputePass();
+ pass.setBindGroup(0, bindGroup);
+
+ t.setComputePipelineAndCallDispatch(
+ pass,
+ t.device.createPipelineLayout({
+ bindGroupLayouts: [bgl]
+ })
+ );
+ pass.end();
+ } else {
+ const pass = t.beginSimpleRenderPass(encoder, t.createTexture().createView());
+ pass.setBindGroup(0, bindGroup);
+ pass.end();
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, secondUseConflicts);
+});
+
+g.test('shader_stages_and_visibility,attachment_write').
+desc(
+ `
+ Test that stage visibility doesn't affect resource usage validation.
+ - Use a texture as sampled, with 'readVisibility' {0,VERTEX,FRAGMENT,COMPUTE}
+ - Use a {same,different} texture as a render pass attachment
+
+ There should be a validation error IFF the same texture was used.
+ `
+).
+params((u) =>
+u.
+beginSubcases().
+combine('secondUseConflicts', [false, true]).
+combine('readVisibility', [
+0,
+GPUConst.ShaderStage.VERTEX,
+GPUConst.ShaderStage.FRAGMENT,
+GPUConst.ShaderStage.COMPUTE]
+)
+).
+fn((t) => {
+ const { readVisibility, 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 view = t.createTexture({ usage }).createView();
+ const view2 = secondUseConflicts ? view : t.createTexture({ usage }).createView();
+ const bgl = t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility: readVisibility, texture: {} }]
+ });
+ const bindGroup = t.device.createBindGroup({
+ layout: bgl,
+ entries: [{ binding: 0, resource: view }]
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = t.beginSimpleRenderPass(encoder, view2);
+ pass.setBindGroup(0, bindGroup);
+ pass.end();
+
+ // Texture usages in bindings with invisible shader stages should be validated. Invisible shader
+ // stages include shader stage with visibility none, compute shader stage in render pass, and
+ // vertex/fragment shader stage in compute pass.
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, secondUseConflicts);
+});
+
+g.test('replaced_binding').
+desc(
+ `
+ Test whether a binding that's been replaced by another setBindGroup call can still
+ cause validation to fail (with a write/write conflict).
+ - In render pass, all setBindGroup calls contribute to the validation even if they're
+ shadowed.
+ - In compute pass, only the bindings visible at dispatchWorkgroups() contribute to validation.
+ `
+).
+params((u) =>
+u.
+combine('compute', [false, true]).
+combine('callDrawOrDispatch', [false, true]).
+combine('entry', [
+{ texture: {} },
+{ storageTexture: { access: 'write-only', format: 'rgba8unorm' } }]
+)
+).
+fn((t) => {
+ const { compute, callDrawOrDispatch, entry } = t.params;
+
+ const sampledView = t.createTexture().createView();
+ const sampledStorageView = t.
+ createTexture({ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING }).
+ createView();
+
+ // Create bindGroup0. It has two bindings. These two bindings use different views/subresources.
+ const bglEntries0 = [
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
+ {
+ binding: 1,
+ visibility: GPUShaderStage.FRAGMENT,
+ ...entry
+ }];
+
+ const bgEntries0 = [
+ { binding: 0, resource: sampledView },
+ { binding: 1, resource: sampledStorageView }];
+
+ const bindGroup0 = t.device.createBindGroup({
+ entries: bgEntries0,
+ layout: t.device.createBindGroupLayout({ entries: bglEntries0 })
+ });
+
+ // 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 encoder = t.device.createCommandEncoder();
+ const pass = compute ?
+ encoder.beginComputePass() :
+ t.beginSimpleRenderPass(encoder, t.createTexture().createView());
+
+ // Set bindGroup0 and bindGroup1. bindGroup0 is replaced by bindGroup1 in the current pass.
+ // But bindings in bindGroup0 should be validated too.
+ pass.setBindGroup(0, bindGroup0);
+ if (callDrawOrDispatch) {
+ const pipeline = compute ? t.createNoOpComputePipeline() : t.createNoOpRenderPipeline();
+ t.setPipeline(pass, pipeline);
+ t.issueDrawOrDispatch(pass);
+ }
+ pass.setBindGroup(0, bindGroup1);
+ pass.end();
+
+ // 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';
+ // 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;
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('bindings_in_bundle').
+desc(
+ `
+ Test the texture usages in bundles by using two bindings of the same texture with various
+ combination of {sampled, storage, render target} usages.
+ `
+).
+params((u) =>
+u.
+combine('type0', ['render-target', ...kTextureBindingTypes]).
+combine('type1', ['render-target', ...kTextureBindingTypes]).
+beginSubcases().
+combine('binding0InBundle', [false, true]).
+combine('binding1InBundle', [false, true]).
+expandWithParams(function* ({ type0, type1 }) {
+ const usageForType = (type) => {
+ switch (type) {
+ case 'multisampled-texture':
+ case 'sampled-texture':
+ return 'TEXTURE_BINDING';
+ case 'writeonly-storage-texture':
+ return 'STORAGE_BINDING';
+ case 'render-target':
+ return 'RENDER_ATTACHMENT';
+ }
+ };
+
+ yield {
+ _usage0: usageForType(type0),
+ _usage1: usageForType(type1),
+ _sampleCount:
+ type0 === 'multisampled-texture' || type1 === 'multisampled-texture' ?
+ 4 :
+ undefined
+ };
+}).
+unless(
+ (p) =>
+ // We can't set 'render-target' in bundle, so we need to exclude it from bundle.
+ // In addition, if both bindings are non-bundle, there is no need to test it because
+ // we have far more comprehensive test cases for that situation in this file.
+ p.binding0InBundle && p.type0 === 'render-target' ||
+ p.binding1InBundle && p.type1 === 'render-target' ||
+ !p.binding0InBundle && !p.binding1InBundle ||
+ // Storage textures can't be multisampled.
+ p._sampleCount !== undefined &&
+ p._sampleCount > 1 && (
+ p._usage0 === 'STORAGE_BINDING' || p._usage1 === 'STORAGE_BINDING') ||
+ // If both are sampled, we create two views of the same texture, so both must be
+ // multisampled.
+ p.type0 === 'multisampled-texture' && p.type1 === 'sampled-texture' ||
+ p.type0 === 'sampled-texture' && p.type1 === 'multisampled-texture'
+)
+).
+fn((t) => {
+ const { binding0InBundle, binding1InBundle, type0, type1, _usage0, _usage1, _sampleCount } =
+ t.params;
+
+ // Two bindings are attached to the same texture view.
+ const usage =
+ _sampleCount === 4 ?
+ GPUTextureUsage[_usage0] | GPUTextureUsage[_usage1] | GPUTextureUsage.RENDER_ATTACHMENT :
+ GPUTextureUsage[_usage0] | GPUTextureUsage[_usage1];
+ const view = t.
+ createTexture({
+ usage,
+ sampleCount: _sampleCount
+ }).
+ createView();
+
+ const bindGroups = [];
+ if (type0 !== 'render-target') {
+ const binding0TexFormat = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ bindGroups[0] = t.createBindGroup(0, view, type0, '2d', {
+ format: binding0TexFormat,
+ sampleType: _sampleCount && 'unfilterable-float'
+ });
+ }
+ if (type1 !== 'render-target') {
+ const binding1TexFormat = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ bindGroups[1] = t.createBindGroup(1, view, type1, '2d', {
+ format: binding1TexFormat,
+ sampleType: _sampleCount && 'unfilterable-float'
+ });
+ }
+
+ const encoder = t.device.createCommandEncoder();
+ // At least one binding is in bundle, which means that its type is not 'render-target'.
+ // As a result, only one binding's type is 'render-target' at most.
+ const pass = t.beginSimpleRenderPass(
+ encoder,
+ type0 === 'render-target' || type1 === 'render-target' ? view : t.createTexture().createView()
+ );
+
+ const bindingsInBundle = [binding0InBundle, binding1InBundle];
+ for (let i = 0; i < 2; i++) {
+ // Create a bundle for each bind group if its bindings is required to be in bundle on purpose.
+ // Otherwise, call setBindGroup directly in pass if needed (when its binding is not
+ // 'render-target').
+ if (bindingsInBundle[i]) {
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm']
+ });
+ bundleEncoder.setBindGroup(i, bindGroups[i]);
+ const bundleInPass = bundleEncoder.finish();
+ pass.executeBundles([bundleInPass]);
+ } else if (bindGroups[i] !== undefined) {
+ pass.setBindGroup(i, bindGroups[i]);
+ }
+ }
+
+ pass.end();
+
+ const isReadOnly = (t) => {
+ switch (t) {
+ case 'sampled-texture':
+ case 'multisampled-texture':
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ let success = false;
+ if (isReadOnly(type0) && isReadOnly(type1)) {
+ success = true;
+ }
+
+ if (type0 === 'writeonly-storage-texture' && type1 === 'writeonly-storage-texture') {
+ success = true;
+ }
+
+ // Resource usages in bundle should be validated.
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('unused_bindings_in_pipeline').
+desc(
+ `
+ Test that for compute pipelines with 'auto' layout, only bindings used by the pipeline count
+ toward the usage scope. For render passes, test the pipeline doesn't matter because only the
+ calls to setBindGroup count toward the usage scope.
+ `
+).
+params((u) =>
+u.
+combine('compute', [false, true]).
+combine('useBindGroup0', [false, true]).
+combine('useBindGroup1', [false, true]).
+combine('setBindGroupsOrder', ['common', 'reversed']).
+combine('setPipeline', ['before', 'middle', 'after', 'none']).
+combine('callDrawOrDispatch', [false, true])
+).
+fn((t) => {
+ const {
+ compute,
+ useBindGroup0,
+ useBindGroup1,
+ setBindGroupsOrder,
+ setPipeline,
+ callDrawOrDispatch
+ } = t.params;
+ const view = t.
+ createTexture({ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING }).
+ createView();
+ const bindGroup0 = t.createBindGroup(0, view, 'sampled-texture', '2d', {
+ format: 'rgba8unorm'
+ });
+ const bindGroup1 = t.createBindGroup(0, view, 'writeonly-storage-texture', '2d', {
+ format: 'rgba8unorm'
+ });
+
+ 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>;
+ ${pp._endif}
+ ${pp._if(useBindGroup1)}
+ @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>;
+ ${pp._endif}
+ @fragment fn main() {}
+ `;
+
+ const wgslCompute = pp`
+ ${pp._if(useBindGroup0)}
+ @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>;
+ ${pp._endif}
+ ${pp._if(useBindGroup1)}
+ @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>;
+ ${pp._endif}
+ @compute @workgroup_size(1) fn main() {}
+ `;
+
+ const pipeline = compute ?
+ t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: wgslCompute
+ }),
+ entryPoint: 'main'
+ }
+ }) :
+ t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: wgslVertex
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: wgslFragment
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = compute ?
+ encoder.beginComputePass() :
+ encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: t.createTexture().createView(),
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ const index0 = setBindGroupsOrder === 'common' ? 0 : 1;
+ const index1 = setBindGroupsOrder === 'common' ? 1 : 0;
+ if (setPipeline === 'before') t.setPipeline(pass, pipeline);
+ pass.setBindGroup(index0, bindGroup0);
+ if (setPipeline === 'middle') t.setPipeline(pass, pipeline);
+ pass.setBindGroup(index1, bindGroup1);
+ if (setPipeline === 'after') t.setPipeline(pass, pipeline);
+ if (callDrawOrDispatch) t.issueDrawOrDispatch(pass);
+ pass.end();
+
+ // Resource usage validation scope is defined by the whole render pass or by dispatch calls.
+ // Regardless of whether or not dispatch is called, in a compute pass, we always succeed
+ // because in this test, none of the bindings are used by the pipeline.
+ // In a render pass, we always fail because usage is based on any bindings used in the
+ // render pass, regardless of whether the pipeline uses them.
+ let success = compute;
+
+ // Also fails if we try to draw/dispatch without a pipeline.
+ if (callDrawOrDispatch && setPipeline === 'none') {
+ success = false;
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('scope,dispatch').
+desc(
+ `
+ Tests that in a compute pass, no usage validation occurs without a dispatch call.
+ {Sets,skips} each of two conflicting bind groups in a pass {with,without} a dispatch call.
+ If both are set, AND there is a dispatch call, validation should fail.
+ `
+).
+params((u) =>
+u.
+combine('dispatch', ['none', 'direct', 'indirect']).
+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 { bindGroup0, bindGroup1, encoder, pass, pipeline } = t.testValidationScope(true);
+ assert(pass instanceof GPUComputePassEncoder);
+ t.setPipeline(pass, pipeline);
+
+ if (setBindGroup0) pass.setBindGroup(0, bindGroup0);
+ if (setBindGroup1) pass.setBindGroup(1, bindGroup1);
+
+ switch (dispatch) {
+ case 'direct':
+ pass.dispatchWorkgroups(1);
+ break;
+ case 'indirect':
+ {
+ const indirectBuffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.INDIRECT });
+ pass.dispatchWorkgroupsIndirect(indirectBuffer, 0);
+ }
+ break;
+ }
+
+ pass.end();
+
+ t.expectValidationError(
+ () => {
+ encoder.finish();
+ },
+ dispatch !== 'none' && setBindGroup0 && setBindGroup1
+ );
+});
+
+g.test('scope,basic,render').
+desc(
+ `
+ Tests that in a render pass, validation occurs even without a pipeline or draw call.
+ {Set,skip} each of two conflicting bind groups. If both are set, validation should fail.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('setBindGroup0', [false, true]).
+combine('setBindGroup1', [false, true])
+).
+fn((t) => {
+ const { setBindGroup0, setBindGroup1 } = t.params;
+
+ const { bindGroup0, bindGroup1, encoder, pass } = t.testValidationScope(false);
+ assert(pass instanceof GPURenderPassEncoder);
+
+ if (setBindGroup0) pass.setBindGroup(0, bindGroup0);
+ if (setBindGroup1) pass.setBindGroup(1, bindGroup1);
+
+ pass.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, setBindGroup0 && setBindGroup1);
+});
+
+g.test('scope,pass_boundary,compute').
+desc(
+ `
+ Test using two conflicting bind groups in separate dispatch calls, {with,without} a pass
+ boundary in between. This should always be valid.
+ `
+).
+paramsSubcasesOnly((u) => u.combine('splitPass', [false, true])).
+fn((t) => {
+ const { splitPass } = t.params;
+
+ const { bindGroupLayouts, bindGroups } = t.makeConflictingBindGroups();
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pipelineUsingBG0 = t.createNoOpComputePipeline(
+ t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayouts[0]]
+ })
+ );
+ const pipelineUsingBG1 = t.createNoOpComputePipeline(
+ t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayouts[1]]
+ })
+ );
+
+ let pass = encoder.beginComputePass();
+ pass.setPipeline(pipelineUsingBG0);
+ pass.setBindGroup(0, bindGroups[0]);
+ pass.dispatchWorkgroups(1);
+ if (splitPass) {
+ pass.end();
+ pass = encoder.beginComputePass();
+ }
+ pass.setPipeline(pipelineUsingBG1);
+ pass.setBindGroup(0, bindGroups[1]);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+
+ // Always valid
+ encoder.finish();
+});
+
+g.test('scope,pass_boundary,render').
+desc(
+ `
+ Test using two conflicting bind groups in separate draw calls, {with,without} a pass
+ boundary in between. This should be valid only if there is a pass boundary.
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combine('splitPass', [false, true]).
+combine('draw', [false, true])
+).
+fn((t) => {
+ const { splitPass, draw } = t.params;
+
+ const { bindGroupLayouts, bindGroups } = t.makeConflictingBindGroups();
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pipelineUsingBG0 = t.createNoOpRenderPipeline(
+ t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayouts[0]]
+ })
+ );
+ const pipelineUsingBG1 = t.createNoOpRenderPipeline(
+ t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayouts[1]]
+ })
+ );
+
+ const attachment = t.createTexture().createView();
+
+ let pass = t.beginSimpleRenderPass(encoder, attachment);
+ pass.setPipeline(pipelineUsingBG0);
+ pass.setBindGroup(0, bindGroups[0]);
+ if (draw) pass.draw(3);
+ if (splitPass) {
+ pass.end();
+ pass = t.beginSimpleRenderPass(encoder, attachment);
+ }
+ pass.setPipeline(pipelineUsingBG1);
+ pass.setBindGroup(0, bindGroups[1]);
+ if (draw) pass.draw(3);
+ pass.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !splitPass);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_common.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_common.spec.js
new file mode 100644
index 0000000000..2a5a002327
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_common.spec.js
@@ -0,0 +1,566 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Texture Usages Validation Tests in Same or Different Render Pass Encoders.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { assert, unreachable } from '../../../../../common/util/util.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ getColorAttachment(
+ texture,
+ textureViewDescriptor)
+ {
+ const view = texture.createView(textureViewDescriptor);
+
+ return {
+ view,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ };
+ }
+
+ createBindGroupForTest(
+ textureView,
+ textureUsage,
+ sampleType)
+ {
+ const bindGroupLayoutEntry = {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT
+ };
+ switch (textureUsage) {
+ case 'texture':
+ bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType };
+ break;
+ case 'storage':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'write-only',
+ format: 'rgba8unorm',
+ viewDimension: '2d-array'
+ };
+ break;
+ default:
+ unreachable();
+ break;
+ }
+ const layout = this.device.createBindGroupLayout({
+ entries: [bindGroupLayoutEntry]
+ });
+ return this.device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource: textureView }]
+ });
+ }
+
+ isRangeNotOverlapped(start0, end0, start1, end1) {
+ assert(start0 <= end0 && start1 <= end1);
+ // There are only two possibilities for two non-overlapped ranges:
+ // [start0, end0] [start1, end1] or
+ // [start1, end1] [start0, end0]
+ return end0 < start1 || end1 < start0;
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kTextureSize = 16;
+const kTextureLevels = 3;
+const kTextureLayers = 3;
+
+g.test('subresources,color_attachments').
+desc(
+ `
+ Test that the different subresource of the same texture are allowed to be used as color
+ attachments in same / different render pass encoder, while the same subresource is only allowed
+ to be used as different color attachments in different render pass encoders.`
+).
+params((u) =>
+u.
+combine('layer0', [0, 1]).
+combine('level0', [0, 1]).
+combine('layer1', [0, 1]).
+combine('level1', [0, 1]).
+combine('inSamePass', [true, false]).
+unless((t) => t.inSamePass && t.level0 !== t.level1)
+).
+fn((t) => {
+ const { layer0, level0, layer1, level1, inSamePass } = t.params;
+
+ const texture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, kTextureLayers],
+ mipLevelCount: kTextureLevels
+ });
+
+ const colorAttachment1 = t.getColorAttachment(texture, {
+ dimension: '2d',
+ baseArrayLayer: layer0,
+ arrayLayerCount: 1,
+ baseMipLevel: level0,
+ mipLevelCount: 1
+ });
+ const colorAttachment2 = t.getColorAttachment(texture, {
+ dimension: '2d',
+ baseArrayLayer: layer1,
+ baseMipLevel: level1,
+ mipLevelCount: 1
+ });
+ const encoder = t.device.createCommandEncoder();
+ if (inSamePass) {
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment1, colorAttachment2]
+ });
+ renderPass.end();
+ } else {
+ const renderPass1 = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment1]
+ });
+ renderPass1.end();
+ const renderPass2 = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment2]
+ });
+ renderPass2.end();
+ }
+
+ const success = inSamePass ? layer0 !== layer1 : true;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('subresources,color_attachment_and_bind_group').
+desc(
+ `
+ Test that when one subresource of a texture is used as a color attachment, it cannot be used in a
+ bind group simultaneously in the same render pass encoder. It is allowed when the bind group is
+ used in another render pass encoder instead of the same one.`
+).
+params((u) =>
+u.
+combine('colorAttachmentLevel', [0, 1]).
+combine('colorAttachmentLayer', [0, 1]).
+combineWithParams([
+{ bgLevel: 0, bgLevelCount: 1 },
+{ bgLevel: 1, bgLevelCount: 1 },
+{ bgLevel: 1, bgLevelCount: 2 }]
+).
+combineWithParams([
+{ bgLayer: 0, bgLayerCount: 1 },
+{ bgLayer: 1, bgLayerCount: 1 },
+{ bgLayer: 1, bgLayerCount: 2 }]
+).
+combine('bgUsage', ['texture', 'storage']).
+unless((t) => t.bgUsage === 'storage' && t.bgLevelCount > 1).
+combine('inSamePass', [true, false])
+).
+fn((t) => {
+ const {
+ colorAttachmentLevel,
+ colorAttachmentLayer,
+ bgLevel,
+ bgLevelCount,
+ bgLayer,
+ bgLayerCount,
+ bgUsage,
+ inSamePass
+ } = t.params;
+
+ const texture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers],
+ mipLevelCount: kTextureLevels
+ });
+ const bindGroupView = texture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: bgLayer,
+ arrayLayerCount: bgLayerCount,
+ baseMipLevel: bgLevel,
+ mipLevelCount: bgLevelCount
+ });
+ const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'float');
+
+ const colorAttachment = t.getColorAttachment(texture, {
+ dimension: '2d',
+ baseArrayLayer: colorAttachmentLayer,
+ arrayLayerCount: 1,
+ baseMipLevel: colorAttachmentLevel,
+ mipLevelCount: 1
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment]
+ });
+ if (inSamePass) {
+ renderPass.setBindGroup(0, bindGroup);
+ renderPass.end();
+ } else {
+ renderPass.end();
+
+ const texture2 = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1],
+ mipLevelCount: 1
+ });
+ const colorAttachment2 = t.getColorAttachment(texture2);
+ const renderPass2 = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment2]
+ });
+ renderPass2.setBindGroup(0, bindGroup);
+ renderPass2.end();
+ }
+
+ const isMipLevelNotOverlapped = t.isRangeNotOverlapped(
+ colorAttachmentLevel,
+ colorAttachmentLevel,
+ bgLevel,
+ bgLevel + bgLevelCount - 1
+ );
+ const isArrayLayerNotOverlapped = t.isRangeNotOverlapped(
+ colorAttachmentLayer,
+ colorAttachmentLayer,
+ bgLayer,
+ bgLayer + bgLayerCount - 1
+ );
+ const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
+
+ const success = inSamePass ? isNotOverlapped : true;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('subresources,depth_stencil_attachment_and_bind_group').
+desc(
+ `
+ Test that when one subresource of a texture is used as a depth stencil attachment, it cannot be
+ used in a bind group simultaneously in the same render pass encoder. It is allowed when the bind
+ group is used in another render pass encoder instead of the same one, or the subresource is used
+ as a read-only depth stencil attachment.`
+).
+params((u) =>
+u.
+combine('dsLevel', [0, 1]).
+combine('dsLayer', [0, 1]).
+combineWithParams([
+{ bgLevel: 0, bgLevelCount: 1 },
+{ bgLevel: 1, bgLevelCount: 1 },
+{ bgLevel: 1, bgLevelCount: 2 }]
+).
+combineWithParams([
+{ bgLayer: 0, bgLayerCount: 1 },
+{ bgLayer: 1, bgLayerCount: 1 },
+{ bgLayer: 1, bgLayerCount: 2 }]
+).
+beginSubcases().
+combine('dsReadOnly', [true, false]).
+combine('bgAspect', ['depth-only', 'stencil-only']).
+combine('inSamePass', [true, false])
+).
+fn((t) => {
+ const {
+ dsLevel,
+ dsLayer,
+ bgLevel,
+ bgLevelCount,
+ bgLayer,
+ bgLayerCount,
+ dsReadOnly,
+ bgAspect,
+ inSamePass
+ } = t.params;
+
+ const texture = t.device.createTexture({
+ format: 'depth24plus-stencil8',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers],
+ mipLevelCount: kTextureLevels
+ });
+ const bindGroupView = texture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: bgLayer,
+ arrayLayerCount: bgLayerCount,
+ baseMipLevel: bgLevel,
+ mipLevelCount: bgLevelCount,
+ aspect: bgAspect
+ });
+ const sampleType = bgAspect === 'depth-only' ? 'depth' : 'uint';
+ const bindGroup = t.createBindGroupForTest(bindGroupView, 'texture', sampleType);
+
+ const attachmentView = texture.createView({
+ dimension: '2d',
+ baseArrayLayer: dsLayer,
+ arrayLayerCount: 1,
+ baseMipLevel: dsLevel,
+ mipLevelCount: 1
+ });
+ const depthStencilAttachment = {
+ view: attachmentView,
+ depthReadOnly: dsReadOnly,
+ depthLoadOp: dsReadOnly ? undefined : 'load',
+ depthStoreOp: dsReadOnly ? undefined : 'store',
+ stencilReadOnly: dsReadOnly,
+ stencilLoadOp: dsReadOnly ? undefined : 'load',
+ stencilStoreOp: dsReadOnly ? undefined : 'store'
+ };
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment
+ });
+ if (inSamePass) {
+ renderPass.setBindGroup(0, bindGroup);
+ renderPass.end();
+ } else {
+ renderPass.end();
+
+ const texture2 = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1],
+ mipLevelCount: 1
+ });
+ const colorAttachment2 = t.getColorAttachment(texture2);
+ const renderPass2 = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment2]
+ });
+ renderPass2.setBindGroup(0, bindGroup);
+ renderPass2.end();
+ }
+
+ const isMipLevelNotOverlapped = t.isRangeNotOverlapped(
+ dsLevel,
+ dsLevel,
+ bgLevel,
+ bgLevel + bgLevelCount - 1
+ );
+ const isArrayLayerNotOverlapped = t.isRangeNotOverlapped(
+ dsLayer,
+ dsLayer,
+ bgLayer,
+ bgLayer + bgLayerCount - 1
+ );
+ const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
+
+ const success = !inSamePass || isNotOverlapped || dsReadOnly;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('subresources,multiple_bind_groups').
+desc(
+ `
+ Test that when one color texture subresource is bound to different bind groups, its list of
+ internal usages within one usage scope can only be a compatible usage list. For texture
+ subresources in bind groups, the compatible usage lists are {TEXTURE_BINDING} and
+ {STORAGE_BINDING}, which means it can only be bound as both TEXTURE_BINDING and STORAGE_BINDING in
+ different render pass encoders, otherwise a validation error will occur.`
+).
+params((u) =>
+u.
+combine('bg0Levels', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('bg0Layers', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('bg1Levels', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('bg1Layers', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('bgUsage0', ['texture', 'storage']).
+combine('bgUsage1', ['texture', 'storage']).
+unless(
+ (t) =>
+ t.bgUsage0 === 'storage' && t.bg0Levels.count > 1 ||
+ t.bgUsage1 === 'storage' && t.bg1Levels.count > 1
+).
+combine('inSamePass', [true, false])
+).
+fn((t) => {
+ const { bg0Levels, bg0Layers, bg1Levels, bg1Layers, bgUsage0, bgUsage1, inSamePass } = t.params;
+
+ const texture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers],
+ mipLevelCount: kTextureLevels
+ });
+ const bg0 = texture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: bg0Layers.base,
+ arrayLayerCount: bg0Layers.count,
+ baseMipLevel: bg0Levels.base,
+ mipLevelCount: bg0Levels.count
+ });
+ const bg1 = texture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: bg1Layers.base,
+ arrayLayerCount: bg1Layers.count,
+ baseMipLevel: bg1Levels.base,
+ mipLevelCount: bg1Levels.count
+ });
+ const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'float');
+ const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'float');
+
+ const colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1],
+ mipLevelCount: 1
+ });
+ const colorAttachment = t.getColorAttachment(colorTexture);
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment]
+ });
+ if (inSamePass) {
+ renderPass.setBindGroup(0, bindGroup0);
+ renderPass.setBindGroup(1, bindGroup1);
+ renderPass.end();
+ } else {
+ renderPass.setBindGroup(0, bindGroup0);
+ renderPass.end();
+
+ const renderPass2 = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment]
+ });
+ renderPass2.setBindGroup(1, bindGroup1);
+ renderPass2.end();
+ }
+
+ const isMipLevelNotOverlapped = t.isRangeNotOverlapped(
+ bg0Levels.base,
+ bg0Levels.base + bg0Levels.count - 1,
+ bg1Levels.base,
+ bg1Levels.base + bg1Levels.count - 1
+ );
+ const isArrayLayerNotOverlapped = t.isRangeNotOverlapped(
+ bg0Layers.base,
+ bg0Layers.base + bg0Layers.count - 1,
+ bg1Layers.base,
+ bg1Layers.base + bg1Layers.count - 1
+ );
+ const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
+
+ const success = !inSamePass || isNotOverlapped || bgUsage0 === bgUsage1;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('subresources,depth_stencil_texture_in_bind_groups').
+desc(
+ `
+ Test that when one depth stencil texture subresource is bound to different bind groups, we can
+ always bind these two bind groups in either the same or different render pass encoder as the depth
+ stencil texture can only be bound as TEXTURE_BINDING in the bind group.`
+).
+params((u) =>
+u.
+combine('view0Levels', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('view0Layers', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('view1Levels', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('view1Layers', [
+{ base: 0, count: 1 },
+{ base: 1, count: 1 },
+{ base: 1, count: 2 }]
+).
+combine('aspect0', ['depth-only', 'stencil-only']).
+combine('aspect1', ['depth-only', 'stencil-only']).
+combine('inSamePass', [true, false])
+).
+fn((t) => {
+ const { view0Levels, view0Layers, view1Levels, view1Layers, aspect0, aspect1, inSamePass } =
+ t.params;
+
+ const texture = t.device.createTexture({
+ format: 'depth24plus-stencil8',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers],
+ mipLevelCount: kTextureLevels
+ });
+ const bindGroupView0 = texture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: view0Layers.base,
+ arrayLayerCount: view0Layers.count,
+ baseMipLevel: view0Levels.base,
+ mipLevelCount: view0Levels.count,
+ aspect: aspect0
+ });
+ const bindGroupView1 = texture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: view1Layers.base,
+ arrayLayerCount: view1Layers.count,
+ baseMipLevel: view1Levels.base,
+ mipLevelCount: view1Levels.count,
+ aspect: aspect1
+ });
+
+ 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 colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1],
+ mipLevelCount: 1
+ });
+ const colorAttachment = t.getColorAttachment(colorTexture);
+ const encoder = t.device.createCommandEncoder();
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment]
+ });
+ if (inSamePass) {
+ renderPass.setBindGroup(0, bindGroup0);
+ renderPass.setBindGroup(1, bindGroup1);
+ renderPass.end();
+ } else {
+ renderPass.setBindGroup(0, bindGroup0);
+ renderPass.end();
+
+ const renderPass2 = encoder.beginRenderPass({
+ colorAttachments: [colorAttachment]
+ });
+ renderPass2.setBindGroup(1, bindGroup1);
+ renderPass2.end();
+ }
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, false);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.js
new file mode 100644
index 0000000000..1cab1548ea
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.js
@@ -0,0 +1,420 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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';
+
+class F extends ValidationTest {
+ createBindGroupLayoutForTest(
+ textureUsage,
+ sampleType,
+ visibility = GPUShaderStage['FRAGMENT'])
+ {
+ const bindGroupLayoutEntry = {
+ binding: 0,
+ visibility
+ };
+
+ switch (textureUsage) {
+ case 'texture':
+ bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType };
+ break;
+ case 'storage':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'write-only',
+ format: 'rgba8unorm',
+ viewDimension: '2d-array'
+ };
+ break;
+ default:
+ unreachable();
+ break;
+ }
+ return this.device.createBindGroupLayout({
+ entries: [bindGroupLayoutEntry]
+ });
+ }
+
+ createBindGroupForTest(
+ textureView,
+ textureUsage,
+ sampleType,
+ visibility = GPUShaderStage['FRAGMENT'])
+ {
+ return this.device.createBindGroup({
+ layout: this.createBindGroupLayoutForTest(textureUsage, sampleType, visibility),
+ entries: [{ binding: 0, resource: textureView }]
+ });
+ }
+}
+
+export const g = makeTestGroup(F);
+
+const kTextureSize = 16;
+const kTextureLayers = 3;
+
+g.test('subresources,set_bind_group_on_same_index_color_texture').
+desc(
+ `
+ Test that when one color texture subresource is bound to different bind groups, whether the bind
+ groups are reset by another compatible ones or not, its list of internal usages within one usage
+ scope can only be a compatible usage list.`
+).
+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' }]
+).
+combine('hasConflict', [true, false])
+).
+fn((t) => {
+ const { useDifferentTextureAsTexture2, baseLayer2, view2Binding, hasConflict } = t.params;
+
+ const texture0 = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers]
+ });
+ // We always bind the first layer of the texture to bindGroup0.
+ const textureView0 = texture0.createView({
+ dimension: '2d-array',
+ 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 texture2 = useDifferentTextureAsTexture2 ?
+ t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers]
+ }) :
+ texture0;
+ const textureView2 = texture2.createView({
+ dimension: '2d-array',
+ baseArrayLayer: baseLayer2,
+ arrayLayerCount: kTextureLayers - baseLayer2
+ });
+ // There should be no conflict between bindGroup0 and validBindGroup2.
+ const validBindGroup2 = t.createBindGroupForTest(textureView2, view2Binding, 'float');
+
+ const colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1]
+ });
+ const encoder = t.device.createCommandEncoder();
+ const renderPassEncoder = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPassEncoder.setBindGroup(0, bindGroup0);
+ renderPassEncoder.setBindGroup(1, bindGroup1);
+ renderPassEncoder.setBindGroup(1, validBindGroup2);
+ renderPassEncoder.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, hasConflict);
+});
+
+g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture').
+desc(
+ `
+ Test that when one depth stencil texture subresource is bound to different bind groups, whether
+ the bind groups are reset by another compatible ones or not, its list of internal usages within
+ one usage scope can only be a compatible usage list.`
+).
+params((u) =>
+u.
+combine('bindAspect', ['depth-only', 'stencil-only']).
+combine('depthStencilReadOnly', [true, false])
+).
+fn((t) => {
+ const { bindAspect, depthStencilReadOnly } = t.params;
+ const depthStencilTexture = t.device.createTexture({
+ format: 'depth24plus-stencil8',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1]
+ });
+
+ const conflictedToNonReadOnlyAttachmentBindGroup = t.createBindGroupForTest(
+ depthStencilTexture.createView({
+ dimension: '2d-array',
+ aspect: bindAspect
+ }),
+ 'texture',
+ bindAspect === 'depth-only' ? 'depth' : 'uint'
+ );
+
+ const colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, 1]
+ });
+ const validBindGroup = t.createBindGroupForTest(
+ colorTexture.createView({
+ dimension: '2d-array'
+ }),
+ 'texture',
+ 'float'
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ const renderPassEncoder = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: depthStencilTexture.createView(),
+ depthReadOnly: depthStencilReadOnly,
+ stencilReadOnly: depthStencilReadOnly
+ }
+ });
+ renderPassEncoder.setBindGroup(0, conflictedToNonReadOnlyAttachmentBindGroup);
+ renderPassEncoder.setBindGroup(0, validBindGroup);
+ renderPassEncoder.end();
+
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !depthStencilReadOnly);
+});
+
+g.test('subresources,set_unused_bind_group').
+desc(
+ `
+ Test that when one texture subresource is bound to different bind groups and the bind groups are
+ 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])).
+fn((t) => {
+ const { inRenderPass, hasConflict } = t.params;
+
+ const texture0 = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, kTextureLayers]
+ });
+ // We always bind the first layer of the texture to bindGroup0.
+ const textureView0 = texture0.createView({
+ dimension: '2d-array',
+ baseArrayLayer: 0,
+ arrayLayerCount: 1
+ });
+ 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 encoder = t.device.createCommandEncoder();
+ const colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1]
+ });
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [t.createBindGroupLayoutForTest(textureUsage0, 'float', visibility)]
+ });
+ if (inRenderPass) {
+ const renderPipeline = t.device.createRenderPipeline({
+ layout: pipelineLayout,
+ 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);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+
+ const renderPassEncoder = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store'
+ }]
+
+ });
+ renderPassEncoder.setBindGroup(0, bindGroup0);
+ renderPassEncoder.setBindGroup(1, bindGroup1);
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.draw(1);
+ renderPassEncoder.end();
+ } else {
+ 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>());
+ }`
+ }),
+ entryPoint: 'main'
+ }
+ });
+ const computePassEncoder = encoder.beginComputePass();
+ computePassEncoder.setBindGroup(0, bindGroup0);
+ computePassEncoder.setBindGroup(1, bindGroup1);
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.dispatchWorkgroups(1);
+ computePassEncoder.end();
+ }
+
+ // In WebGPU SPEC (Chapter 3.4.5, 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
+ // scope if it is potentially accessible by the command. State-setting compute pass commands,
+ // like setBindGroup(index, bindGroup, dynamicOffsets), do not contribute directly to a usage
+ // scope.
+ // - One render pass is one usage scope. A subresource is "used" in the usage scope if it’s
+ // 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;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+});
+
+g.test('subresources,texture_usages_in_copy_and_render_pass').
+desc(
+ `
+ Test that using one texture subresource in a render pass encoder and a copy command is always
+ allowed as WebGPU SPEC (chapter 3.4.5) defines that out of any pass encoder, each command always
+ belongs to one usage scope.`
+).
+params((u) =>
+u.
+combine('usage0', [
+'copy-src',
+'copy-dst',
+'texture',
+'storage',
+'color-attachment']
+).
+combine('usage1', [
+'copy-src',
+'copy-dst',
+'texture',
+'storage',
+'color-attachment']
+).
+filter(
+ ({ usage0, usage1 }) =>
+ usage0 === 'copy-src' ||
+ usage0 === 'copy-dst' ||
+ usage1 === 'copy-src' ||
+ usage1 === 'copy-dst'
+)
+).
+fn((t) => {
+ const { usage0, usage1 } = t.params;
+
+ const texture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.STORAGE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1]
+ });
+
+ const UseTextureOnCommandEncoder = (
+ texture,
+ usage,
+ encoder) =>
+ {
+ switch (usage) {
+ case 'copy-src':{
+ const buffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ encoder.copyTextureToBuffer({ texture }, { buffer }, [1, 1, 1]);
+ break;
+ }
+ case 'copy-dst':{
+ const buffer = t.createBufferWithState('valid', {
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ encoder.copyBufferToTexture({ buffer }, { texture }, [1, 1, 1]);
+ break;
+ }
+ case 'color-attachment':{
+ const renderPassEncoder = encoder.beginRenderPass({
+ colorAttachments: [{ view: texture.createView(), loadOp: 'load', storeOp: 'store' }]
+ });
+ renderPassEncoder.end();
+ break;
+ }
+ case 'texture':
+ case 'storage':{
+ const colorTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ size: [kTextureSize, kTextureSize, 1]
+ });
+ const renderPassEncoder = encoder.beginRenderPass({
+ colorAttachments: [
+ { view: colorTexture.createView(), loadOp: 'load', storeOp: 'store' }]
+
+ });
+ const bindGroup = t.createBindGroupForTest(
+ texture.createView({
+ dimension: '2d-array'
+ }),
+ usage,
+ 'float'
+ );
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.end();
+ break;
+ }
+ }
+ };
+ const encoder = t.device.createCommandEncoder();
+ UseTextureOnCommandEncoder(texture, usage0, encoder);
+ UseTextureOnCommandEncoder(texture, usage1, encoder);
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, false);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/entry_point.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/entry_point.spec.js
new file mode 100644
index 0000000000..a76f5550aa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/entry_point.spec.js
@@ -0,0 +1,117 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This tests entry point validation of compute/render pipelines and their shader modules.
+
+The entryPoint in shader module include standard "main" and others.
+The entryPoint assigned in descriptor include:
+- Matching case (control case)
+- Empty string
+- Mistyping
+- Containing invalid char, including space and control codes (Null character)
+- 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
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kDefaultVertexShaderCode, getShaderWithEntryPoint } from '../../../util/shader.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+const kEntryPointTestCases = [
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'main' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: '' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'main\0' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'main\0a' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'mian' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'main ' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'ma in' },
+{ shaderModuleEntryPoint: 'main', stageEntryPoint: 'main\n' },
+{ shaderModuleEntryPoint: 'mian', stageEntryPoint: 'mian' },
+{ shaderModuleEntryPoint: 'mian', stageEntryPoint: 'main' },
+{ shaderModuleEntryPoint: 'mainmain', stageEntryPoint: 'mainmain' },
+{ shaderModuleEntryPoint: 'mainmain', stageEntryPoint: 'foo' },
+{ shaderModuleEntryPoint: 'main_t12V3', stageEntryPoint: 'main_t12V3' },
+{ shaderModuleEntryPoint: 'main_t12V3', stageEntryPoint: 'main_t12V5' },
+{ shaderModuleEntryPoint: 'main_t12V3', stageEntryPoint: '_main_t12V3' },
+{ shaderModuleEntryPoint: 'séquençage', stageEntryPoint: 'séquençage' },
+{ shaderModuleEntryPoint: 'séquençage', stageEntryPoint: 'séquençage' }];
+
+
+g.test('compute').
+desc(
+ `
+Tests calling createComputePipeline(Async) with valid vertex stage shader and different entryPoints,
+and check that the APIs only accept matching entryPoint.
+`
+).
+params((u) => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases)).
+fn((t) => {
+ const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const descriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: getShaderWithEntryPoint('compute', shaderModuleEntryPoint)
+ }),
+ entryPoint: stageEntryPoint
+ }
+ };
+ const _success = shaderModuleEntryPoint === stageEntryPoint;
+ t.doCreateComputePipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('vertex').
+desc(
+ `
+Tests calling createRenderPipeline(Async) with valid vertex stage shader and different entryPoints,
+and check that the APIs only accept matching entryPoint.
+`
+).
+params((u) => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases)).
+fn((t) => {
+ const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const descriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: getShaderWithEntryPoint('vertex', shaderModuleEntryPoint)
+ }),
+ entryPoint: stageEntryPoint
+ }
+ };
+ const _success = shaderModuleEntryPoint === stageEntryPoint;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+});
+
+g.test('fragment').
+desc(
+ `
+Tests calling createRenderPipeline(Async) with valid fragment stage shader and different entryPoints,
+and check that the APIs only accept matching entryPoint.
+`
+).
+params((u) => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases)).
+fn((t) => {
+ const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const descriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kDefaultVertexShaderCode
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: getShaderWithEntryPoint('fragment', shaderModuleEntryPoint)
+ }),
+ entryPoint: stageEntryPoint,
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ };
+ const _success = shaderModuleEntryPoint === stageEntryPoint;
+ t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/overrides.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/overrides.spec.js
new file mode 100644
index 0000000000..b48066f8a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/shader_module/overrides.spec.js
@@ -0,0 +1,96 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+This tests overrides numeric identifiers should not conflict.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('id_conflict').
+desc(
+ `
+Tests that overrides' explicit numeric identifier should not conflict.
+`
+).
+fn((t) => {
+ t.expectValidationError(() => {
+ t.device.createShaderModule({
+ code: `
+@id(1234) override c0: u32;
+@id(4321) override c1: u32;
+
+@compute @workgroup_size(1) fn main() {
+ // make sure the overridable constants are not optimized out
+ _ = c0;
+ _ = c1;
+}
+ `
+ });
+ }, false);
+
+ t.expectValidationError(() => {
+ t.device.createShaderModule({
+ code: `
+@id(1234) override c0: u32;
+@id(1234) override c1: u32;
+
+@compute @workgroup_size(1) fn main() {
+ // make sure the overridable constants are not optimized out
+ _ = c0;
+ _ = c1;
+}
+ `
+ });
+ }, true);
+});
+
+g.test('name_conflict').
+desc(
+ `
+Tests that overrides' variable name should not conflict, regardless of their numeric identifiers.
+`
+).
+fn((t) => {
+ t.expectValidationError(() => {
+ t.device.createShaderModule({
+ code: `
+override c0: u32;
+override c0: u32;
+
+@compute @workgroup_size(1) fn main() {
+ // make sure the overridable constants are not optimized out
+ _ = c0;
+}
+ `
+ });
+ }, true);
+
+ t.expectValidationError(() => {
+ t.device.createShaderModule({
+ code: `
+@id(1) override c0: u32;
+override c0: u32;
+
+@compute @workgroup_size(1) fn main() {
+ // make sure the overridable constants are not optimized out
+ _ = c0;
+}
+ `
+ });
+ }, true);
+
+ t.expectValidationError(() => {
+ t.device.createShaderModule({
+ code: `
+@id(1) override c0: u32;
+@id(2) override c0: u32;
+
+@compute @workgroup_size(1) fn main() {
+ // make sure the overridable constants are not optimized out
+ _ = c0;
+}
+ `
+ });
+ }, true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/state/device_lost/destroy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/state/device_lost/destroy.spec.js
new file mode 100644
index 0000000000..d5087f9315
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/state/device_lost/destroy.spec.js
@@ -0,0 +1,1170 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for device lost induced via destroy.
+ - Tests that prior to device destruction, valid APIs do not generate errors (control case).
+ - After device destruction, runs the same APIs. No expected observable results, so test crash or future failures are the only current failure indicators.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../common/util/util.js';
+import {
+ allBindingEntries,
+ bindingTypeInfo,
+ kBindableResources,
+ kBufferUsageKeys,
+ kBufferUsageInfo,
+ kBufferUsageCopy,
+ kBufferUsageCopyInfo,
+ kQueryTypes,
+ kTextureUsageType,
+ kTextureUsageTypeInfo,
+ kTextureUsageCopy,
+ kTextureUsageCopyInfo,
+ kShaderStageKeys } from
+'../../../../capability_info.js';
+import {
+ kCompressedTextureFormats,
+ kRegularTextureFormats,
+ kRenderableColorTextureFormats,
+ kTextureFormatInfo } from
+'../../../../format_info.js';
+
+import {
+ createCanvas,
+ kAllCanvasTypes,
+ kValidCanvasContextIds } from
+'../../../../util/create_elements.js';
+import {
+ startPlayingAndWaitForVideo,
+ getVideoElement,
+ getVideoFrameFromVideoElement } from
+'../../../../web_platform/util.js';
+import { ValidationTest } from '../../validation_test.js';
+
+const kCommandValidationStages = ['finish', 'submit'];
+
+
+class DeviceDestroyTests extends ValidationTest {
+ /**
+ * Expects that `fn` does not produce any errors before the device is destroyed, and then calls
+ * `fn` after the device is destroyed without any specific expectation. If `awaitLost` is true, we
+ * also wait for device.lost to resolve before executing `fn` in the destroy case.
+ */
+ async executeAfterDestroy(fn, awaitLost) {
+ this.expectDeviceLost('destroyed');
+
+ this.expectValidationError(fn, false);
+ this.device.destroy();
+ if (awaitLost) {
+ const lostInfo = await this.device.lost;
+ this.expect(lostInfo.reason === 'destroyed');
+ }
+ fn();
+ }
+
+ /**
+ * Expects that encoders can finish and submit the resulting commands before the device is
+ * destroyed, then repeats the same process after the device is destroyed without any specific
+ * expectations.
+ * There are two valid stages: 'finish' and 'submit'.
+ * 'finish': Tests [encode, finish] and [encoder, destroy, finish]
+ * 'submit': Tests [encoder, finish, submit] and [encoder, finish, destroy, submit]
+ */
+ async executeCommandsAfterDestroy(
+ stage,
+ awaitLost,
+ encoderType,
+ fn)
+ {
+ this.expectDeviceLost('destroyed');
+
+ switch (stage) {
+ case 'finish':{
+ // Control case
+ fn(this.createEncoder(encoderType)).validateFinish(true);
+ // Validation case
+ const encoder = fn(this.createEncoder(encoderType));
+ await this.executeAfterDestroy(() => {
+ encoder.finish();
+ }, awaitLost);
+ break;
+ }
+ case 'submit':{
+ // Control case
+ fn(this.createEncoder(encoderType)).validateFinishAndSubmit(true, true);
+ // Validation case
+ const commands = fn(this.createEncoder(encoderType)).validateFinish(true);
+ await this.executeAfterDestroy(() => {
+ this.queue.submit([commands]);
+ }, awaitLost);
+ break;
+ }
+ }
+ }
+}
+
+export const g = makeTestGroup(DeviceDestroyTests);
+
+g.test('createBuffer').
+desc(
+ `
+Tests creating buffers on destroyed device. Tests valid combinations of:
+ - Various usages
+ - Mapped at creation or not
+ `
+).
+params((u) =>
+u.
+combine('usageType', kBufferUsageKeys).
+
+combine('usageCopy', kBufferUsageCopy).
+combine('awaitLost', [true, false]).
+filter(({ usageType, usageCopy }) => {
+ if (usageType === 'COPY_SRC' || usageType === 'COPY_DST') {
+ return false;
+ }
+ if (usageType === 'MAP_READ') {
+ return usageCopy === 'COPY_NONE' || usageCopy === 'COPY_DST';
+ }
+ if (usageType === 'MAP_WRITE') {
+ return usageCopy === 'COPY_NONE' || usageCopy === 'COPY_SRC';
+ }
+ return true;
+}).
+combine('mappedAtCreation', [true, false])
+).
+fn(async (t) => {
+ const { awaitLost, usageType, usageCopy, mappedAtCreation } = t.params;
+ await t.executeAfterDestroy(() => {
+ t.device.createBuffer({
+ size: 16,
+ usage: kBufferUsageInfo[usageType] | kBufferUsageCopyInfo[usageCopy],
+ mappedAtCreation
+ });
+ }, awaitLost);
+});
+
+g.test('createTexture,2d,uncompressed_format').
+desc(
+ `
+Tests creating 2d uncompressed textures on destroyed device. Tests valid combinations of:
+ - Various uncompressed texture formats
+ - Various usages
+ `
+).
+params((u) =>
+u.
+combine('format', kRegularTextureFormats).
+
+combine('usageType', kTextureUsageType).
+combine('usageCopy', kTextureUsageCopy).
+combine('awaitLost', [true, false]).
+filter(({ format, usageType }) => {
+ const info = kTextureFormatInfo[format];
+ return !(
+ !info.colorRender && usageType === 'render' ||
+ !info.color.storage && usageType === 'storage');
+
+})
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn(async (t) => {
+ const { awaitLost, format, usageType, usageCopy } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+ await t.executeAfterDestroy(() => {
+ t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy],
+ format
+ });
+ }, awaitLost);
+});
+
+g.test('createTexture,2d,compressed_format').
+desc(
+ `
+Tests creating 2d compressed textures on destroyed device. Tests valid combinations of:
+ - Various compressed texture formats
+ - Various usages
+ `
+).
+params((u) =>
+u.
+combine('format', kCompressedTextureFormats).
+
+combine('usageType', kTextureUsageType).
+combine('usageCopy', kTextureUsageCopy).
+combine('awaitLost', [true, false]).
+filter(({ format, usageType }) => {
+ const info = kTextureFormatInfo[format];
+ return !(
+ !info.colorRender && usageType === 'render' ||
+ !info.color.storage && usageType === 'storage');
+
+})
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn(async (t) => {
+ const { awaitLost, format, usageType, usageCopy } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+ await t.executeAfterDestroy(() => {
+ t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy],
+ format
+ });
+ }, awaitLost);
+});
+
+g.test('createView,2d,uncompressed_format').
+desc(
+ `
+Tests creating texture views on 2d uncompressed textures from destroyed device. Tests valid combinations of:
+ - Various uncompressed texture formats
+ - Various usages
+ `
+).
+params((u) =>
+u.
+combine('format', kRegularTextureFormats).
+
+combine('usageType', kTextureUsageType).
+combine('usageCopy', kTextureUsageCopy).
+combine('awaitLost', [true, false]).
+filter(({ format, usageType }) => {
+ const info = kTextureFormatInfo[format];
+ return !(
+ !info.colorRender && usageType === 'render' ||
+ !info.color.storage && usageType === 'storage');
+
+})
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn(async (t) => {
+ const { awaitLost, format, usageType, usageCopy } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+ const texture = t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy],
+ format
+ });
+ await t.executeAfterDestroy(() => {
+ texture.createView({ format });
+ }, awaitLost);
+});
+
+g.test('createView,2d,compressed_format').
+desc(
+ `
+Tests creating texture views on 2d compressed textures from destroyed device. Tests valid combinations of:
+ - Various compressed texture formats
+ - Various usages
+ `
+).
+params((u) =>
+u.
+combine('format', kCompressedTextureFormats).
+
+combine('usageType', kTextureUsageType).
+combine('usageCopy', kTextureUsageCopy).
+combine('awaitLost', [true, false]).
+filter(({ format, usageType }) => {
+ const info = kTextureFormatInfo[format];
+ return !(
+ !info.colorRender && usageType === 'render' ||
+ !info.color.storage && usageType === 'storage');
+
+})
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn(async (t) => {
+ const { awaitLost, format, usageType, usageCopy } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+ const texture = t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: kTextureUsageTypeInfo[usageType] | kTextureUsageCopyInfo[usageCopy],
+ format
+ });
+ await t.executeAfterDestroy(() => {
+ texture.createView({ format });
+ }, awaitLost);
+});
+
+g.test('createSampler').
+desc(
+ `
+Tests creating samplers on destroyed device.
+ `
+).
+params((u) => u.combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost } = t.params;
+ await t.executeAfterDestroy(() => {
+ t.device.createSampler();
+ }, awaitLost);
+});
+
+g.test('createBindGroupLayout').
+desc(
+ `
+Tests creating bind group layouts on destroyed device. Tests valid combinations of:
+ - Various valid binding entries
+ - Maximum set of visibility for each binding entry
+ `
+).
+params((u) => u.combine('entry', allBindingEntries(false)).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost, entry } = t.params;
+ const visibility = bindingTypeInfo(entry).validStages;
+ await t.executeAfterDestroy(() => {
+ t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility, ...entry }]
+ });
+ }, awaitLost);
+});
+
+g.test('createBindGroup').
+desc(
+ `
+Tests creating bind group on destroyed device. Tests valid combinations of:
+ - Various bound resource types
+ - Various valid binding entries
+ - Maximum set of visibility for each binding entry
+ `
+).
+desc(`A destroyed device should not be able to create any valid bind groups.`).
+params((u) =>
+u.
+combine('resourceType', kBindableResources).
+combine('entry', allBindingEntries(false)).
+filter(({ resourceType, entry }) => {
+ const info = bindingTypeInfo(entry);
+ switch (info.resource) {
+ // Either type of sampler may be bound to a filtering sampler binding.
+ case 'filtSamp':
+ return resourceType === 'filtSamp' || resourceType === 'nonFiltSamp';
+ // But only non-filtering samplers can be used with non-filtering sampler bindings.
+ case 'nonFiltSamp':
+ return resourceType === 'nonFiltSamp';
+ default:
+ return info.resource === resourceType;
+ }
+}).
+
+combine('awaitLost', [true, false])
+).
+fn(async (t) => {
+ const { awaitLost, resourceType, entry } = t.params;
+ const visibility = bindingTypeInfo(entry).validStages;
+ const layout = t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility, ...entry }]
+ });
+ const resource = t.getBindingResource(resourceType);
+ await t.executeAfterDestroy(() => {
+ t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] });
+ }, awaitLost);
+});
+
+g.test('createPipelineLayout').
+desc(
+ `
+Tests creating pipeline layouts on destroyed device. Tests valid combinations of:
+ - Various bind groups with valid binding entries
+ - Maximum set of visibility for each binding entry
+ `
+).
+params((u) => u.combine('entry', allBindingEntries(false)).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost, entry } = t.params;
+ const visibility = bindingTypeInfo(entry).validStages;
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility, ...entry }]
+ });
+ await t.executeAfterDestroy(() => {
+ t.device.createPipelineLayout({
+ bindGroupLayouts: [bindGroupLayout]
+ });
+ }, awaitLost);
+});
+
+g.test('createShaderModule').
+desc(
+ `
+Tests creating shader modules on destroyed device.
+ - Tests all shader stages: vertex, fragment, compute
+ `
+).
+params((u) => u.combine('stage', kShaderStageKeys).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost, stage } = t.params;
+ await t.executeAfterDestroy(() => {
+ t.device.createShaderModule({ code: t.getNoOpShaderCode(stage) });
+ }, awaitLost);
+});
+
+g.test('createComputePipeline').
+desc(
+ `
+Tests creating compute pipeline on destroyed device.
+ - Tests with a valid no-op compute shader
+ `
+).
+params((u) => u.combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost } = t.params;
+ const cShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('COMPUTE') });
+ await t.executeAfterDestroy(() => {
+ t.device.createComputePipeline({
+ layout: 'auto',
+ compute: { module: cShader, entryPoint: 'main' }
+ });
+ }, awaitLost);
+});
+
+g.test('createRenderPipeline').
+desc(
+ `
+Tests creating render pipeline on destroyed device.
+ - Tests with valid no-op vertex and fragment shaders
+ `
+).
+params((u) => u.combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost } = t.params;
+ const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') });
+ const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') });
+ await t.executeAfterDestroy(() => {
+ t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module: vShader, entryPoint: 'main' },
+ fragment: {
+ module: fShader,
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ }
+ });
+ }, awaitLost);
+});
+
+g.test('createComputePipelineAsync').
+desc(
+ `
+Tests creating a pipeline asynchronously while destroying the device and on a destroyed device
+- valid={true, false}, use an invalid or valid pipeline descriptor
+- awaitLost={true, false}, check results before/after waiting for the device lost promise
+ `
+).
+params((u) => u.combine('valid', [true, false]).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { valid, awaitLost } = t.params;
+ const cShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('COMPUTE') });
+ const fn = () =>
+ t.device.createComputePipelineAsync({
+ layout: 'auto',
+ compute: { module: cShader, entryPoint: valid ? 'main' : 'does_not_exist' }
+ });
+
+ // Kick off async creation
+ const p = fn();
+
+ // Track whether or not the device is lost.
+ let isLost = false;
+ void t.device.lost.then(() => {
+ isLost = true;
+ });
+
+ if (valid) {
+ // The async creation should resolve successfully.
+ t.shouldResolve(
+ (async () => {
+ const pipeline = await p;
+ assert(pipeline instanceof GPUComputePipeline, 'Pipeline was not a GPUComputePipeline');
+ })()
+ );
+ } else {
+ // The async creation should resolve successfully if the device is lost.
+ // If the device is not lost, it should see a validation error.
+ // Note: this could be a race!
+ t.shouldResolve(
+ p.then(
+ (pipeline) => {
+ assert(
+ isLost,
+ 'Invalid async creation should "succeed" if the device is already lost.'
+ );
+ assert(pipeline instanceof GPUComputePipeline, 'Pipeline was not a GPUComputePipeline');
+ },
+ (err) => {
+ assert(
+ !isLost,
+ 'Invalid async creation should only fail if the device is not yet lost.'
+ );
+ assert(err instanceof GPUPipelineError, 'Error was not a GPUPipelineError');
+ assert(err.reason === 'validation', 'Expected validation error');
+ }
+ )
+ );
+ }
+
+ // Destroy the device, and expect it to be lost.
+ t.expectDeviceLost('destroyed');
+ t.device.destroy();
+ if (awaitLost) {
+ const lostInfo = await t.device.lost;
+ t.expect(lostInfo.reason === 'destroyed');
+ }
+
+ // After device destroy, creation should still resolve successfully.
+ t.shouldResolve(
+ (async () => {
+ const pipeline = await fn();
+ assert(pipeline instanceof GPUComputePipeline, 'Pipeline was not a GPUComputePipeline');
+ })()
+ );
+});
+
+g.test('createRenderPipelineAsync').
+desc(
+ `
+Tests creating a pipeline asynchronously while destroying the device and on a destroyed device
+- valid={true, false}, use an invalid or valid pipeline descriptor
+- awaitLost={true, false}, check results before/after waiting for the device lost promise
+ `
+).
+params((u) => u.combine('valid', [true, false]).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { valid, awaitLost } = t.params;
+ const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') });
+ const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') });
+ const fn = () =>
+ t.device.createRenderPipelineAsync({
+ layout: 'auto',
+ vertex: { module: vShader, entryPoint: 'main' },
+ fragment: {
+ module: fShader,
+ entryPoint: valid ? 'main' : 'does_not_exist',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ }
+ });
+
+ // Kick off async creation
+ const p = fn();
+
+ // Track whether or not the device is lost.
+ let isLost = false;
+ void t.device.lost.then(() => {
+ isLost = true;
+ });
+
+ if (valid) {
+ // The async creation should resolve successfully.
+ t.shouldResolve(
+ (async () => {
+ const pipeline = await p;
+ assert(pipeline instanceof GPURenderPipeline, 'Pipeline was not a GPURenderPipeline');
+ })()
+ );
+ } else {
+ // The async creation should resolve successfully if the device is lost.
+ // If the device is not lost, it should see a validation error.
+ // Note: this could be a race!
+ t.shouldResolve(
+ p.then(
+ (pipeline) => {
+ assert(
+ isLost,
+ 'Invalid async creation should "succeed" if the device is already lost.'
+ );
+ assert(pipeline instanceof GPURenderPipeline, 'Pipeline was not a GPURenderPipeline');
+ },
+ (err) => {
+ assert(
+ !isLost,
+ 'Invalid async creation should only fail if the device is not yet lost.'
+ );
+ assert(err instanceof GPUPipelineError, 'Error was not a GPUPipelineError');
+ assert(err.reason === 'validation', 'Expected validation error');
+ }
+ )
+ );
+ }
+
+ // Destroy the device, and expect it to be lost.
+ t.expectDeviceLost('destroyed');
+ t.device.destroy();
+ if (awaitLost) {
+ const lostInfo = await t.device.lost;
+ t.expect(lostInfo.reason === 'destroyed');
+ }
+
+ // After device destroy, creation should still resolve successfully.
+ t.shouldResolve(
+ (async () => {
+ const pipeline = await fn();
+ assert(pipeline instanceof GPURenderPipeline, 'Pipeline was not a GPURenderPipeline');
+ })()
+ );
+});
+
+g.test('createCommandEncoder').
+desc(
+ `
+Tests creating command encoders on destroyed device.
+ `
+).
+params((u) => u.combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost } = t.params;
+ await t.executeAfterDestroy(() => {
+ t.device.createCommandEncoder();
+ }, awaitLost);
+});
+
+g.test('createRenderBundleEncoder').
+desc(
+ `
+Tests creating render bundle encoders on destroyed device.
+ - Tests various renderable texture color formats
+ `
+).
+params((u) =>
+u.
+combine('format', kRenderableColorTextureFormats).
+
+combine('awaitLost', [true, false])
+).
+fn(async (t) => {
+ const { awaitLost, format } = t.params;
+ await t.executeAfterDestroy(() => {
+ t.device.createRenderBundleEncoder({ colorFormats: [format] });
+ }, awaitLost);
+});
+
+g.test('createQuerySet').
+desc(
+ `
+Tests creating query sets on destroyed device.
+ - Tests various query set types
+ `
+).
+params((u) => u.combine('type', kQueryTypes).combine('awaitLost', [true, false])).
+beforeAllSubcases((t) => {
+ const { type } = t.params;
+ t.selectDeviceForQueryTypeOrSkipTestCase(type);
+}).
+fn(async (t) => {
+ const { awaitLost, type } = t.params;
+ await t.executeAfterDestroy(() => {
+ t.device.createQuerySet({ type, count: 4 });
+ }, awaitLost);
+});
+
+g.test('importExternalTexture').
+desc(
+ `
+Tests import external texture on destroyed device. Tests valid combinations of:
+ - Various valid source type
+ `
+).
+params((u) =>
+u.
+combine('sourceType', ['VideoElement', 'VideoFrame']).
+
+combine('awaitLost', [true, false])
+).
+fn(async (t) => {
+ const { awaitLost, sourceType } = t.params;
+
+ const videoElement = getVideoElement(t, 'four-colors-vp9-bt601.webm');
+ if (!('requestVideoFrameCallback' in videoElement)) {
+ t.skip('HTMLVideoElement.requestVideoFrameCallback is not supported');
+ }
+
+ let source;
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+
+ await t.executeAfterDestroy(() => {
+ t.device.createBindGroup({
+ layout: t.device.createBindGroupLayout({
+ entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, externalTexture: {} }]
+ }),
+ entries: [
+ {
+ binding: 0,
+ resource: t.device.importExternalTexture({
+
+ source: source
+ })
+ }]
+
+ });
+ }, awaitLost);
+ });
+});
+
+g.test('command,copyBufferToBuffer').
+desc(
+ `
+Tests copyBufferToBuffer command with various uncompressed formats on destroyed device.
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const kBufferSize = 16;
+ const src = t.device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ const dst = t.device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', (maker) => {
+ maker.encoder.copyBufferToBuffer(src, 0, dst, 0, kBufferSize);
+ return maker;
+ });
+});
+
+g.test('command,copyBufferToTexture').
+desc(
+ `
+Tests copyBufferToTexture command on destroyed device.
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const format = 'rgba32uint';
+ const {
+ color: { bytes: bytesPerBlock },
+ blockWidth,
+ blockHeight
+ } = kTextureFormatInfo[format];
+ const src = {
+ buffer: t.device.createBuffer({
+ size: bytesPerBlock,
+ usage: GPUBufferUsage.COPY_SRC
+ })
+ };
+ const dst = {
+ texture: t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: GPUTextureUsage.COPY_DST,
+ format
+ })
+ };
+ const copySize = { width: blockWidth, height: blockHeight };
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', (maker) => {
+ maker.encoder.copyBufferToTexture(src, dst, copySize);
+ return maker;
+ });
+});
+
+g.test('command,copyTextureToBuffer').
+desc(
+ `
+Tests copyTextureToBuffer command on destroyed device.
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const format = 'rgba32uint';
+ const {
+ color: { bytes: bytesPerBlock },
+ blockWidth,
+ blockHeight
+ } = kTextureFormatInfo[format];
+ const src = {
+ texture: t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: GPUTextureUsage.COPY_SRC,
+ format
+ })
+ };
+ const dst = {
+ buffer: t.device.createBuffer({
+ size: bytesPerBlock,
+ usage: GPUBufferUsage.COPY_DST
+ })
+ };
+ const copySize = { width: blockWidth, height: blockHeight };
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', (maker) => {
+ maker.encoder.copyTextureToBuffer(src, dst, copySize);
+ return maker;
+ });
+});
+
+g.test('command,copyTextureToTexture').
+desc(
+ `
+Tests copyTextureToTexture command on destroyed device.
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const format = 'rgba32uint';
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+ const src = {
+ texture: t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: GPUTextureUsage.COPY_SRC,
+ format
+ })
+ };
+ const dst = {
+ texture: t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: GPUBufferUsage.COPY_DST,
+ format
+ })
+ };
+ const copySize = { width: blockWidth, height: blockHeight };
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', (maker) => {
+ maker.encoder.copyTextureToTexture(src, dst, copySize);
+ return maker;
+ });
+});
+
+g.test('command,clearBuffer').
+desc(
+ `
+Tests encoding and finishing a clearBuffer command on destroyed device.
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const kBufferSize = 16;
+ const buffer = t.device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', (maker) => {
+ maker.encoder.clearBuffer(buffer, 0, kBufferSize);
+ return maker;
+ });
+});
+
+g.test('command,writeTimestamp').
+desc(
+ `
+Tests encoding and finishing a writeTimestamp command on destroyed device.
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) =>
+u.
+combine('type', kQueryTypes).
+
+combine('stage', kCommandValidationStages).
+combine('awaitLost', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { type } = t.params;
+
+ // writeTimestamp is only available for devices that enable the 'timestamp-query' feature.
+ const queryTypes = ['timestamp'];
+ if (type !== 'timestamp') {
+ queryTypes.push(type);
+ }
+
+ t.selectDeviceForQueryTypeOrSkipTestCase(queryTypes);
+}).
+fn(async (t) => {
+ 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);
+ return maker;
+ });
+});
+
+g.test('command,resolveQuerySet').
+desc(
+ `
+Tests encoding and finishing a resolveQuerySet command on destroyed device.
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const kQueryCount = 2;
+ const querySet = t.createQuerySetWithState('valid');
+ const destination = t.createBufferWithState('valid', {
+ size: kQueryCount * 8,
+ usage: GPUBufferUsage.QUERY_RESOLVE
+ });
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', (maker) => {
+ maker.encoder.resolveQuerySet(querySet, 0, 1, destination, 0);
+ return maker;
+ });
+});
+
+g.test('command,computePass,dispatch').
+desc(
+ `
+Tests encoding and dispatching a simple valid compute pass on destroyed device.
+ - Binds valid pipeline and bindgroups, then dispatches
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const cShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('COMPUTE') });
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: { module: cShader, entryPoint: 'main' }
+ });
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'compute pass', (maker) => {
+ maker.encoder.setPipeline(pipeline);
+ maker.encoder.dispatchWorkgroups(1);
+ return maker;
+ });
+});
+
+g.test('command,renderPass,draw').
+desc(
+ `
+Tests encoding and finishing a simple valid render pass on destroyed device.
+ - Binds valid pipeline and bindgroups, then draws
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') });
+ const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module: vShader, entryPoint: 'main' },
+ fragment: {
+ module: fShader,
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ }
+ });
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'render pass', (maker) => {
+ maker.encoder.setPipeline(pipeline);
+ maker.encoder.draw(0);
+ return maker;
+ });
+});
+
+g.test('command,renderPass,renderBundle').
+desc(
+ `
+Tests encoding and drawing a render pass including a render bundle on destroyed device.
+ - Binds valid pipeline and bindgroups, executes render bundle, then draws
+ - Tests finishing encoding on destroyed device
+ - Tests submitting command on destroyed device
+ `
+).
+params((u) => u.combine('stage', kCommandValidationStages).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { stage, awaitLost } = t.params;
+ const vShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX') });
+ const fShader = t.device.createShaderModule({ code: t.getNoOpShaderCode('FRAGMENT') });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module: vShader, entryPoint: 'main' },
+ fragment: {
+ module: fShader,
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ }
+ });
+ await t.executeCommandsAfterDestroy(stage, awaitLost, 'render bundle', (maker) => {
+ maker.encoder.setPipeline(pipeline);
+ maker.encoder.draw(0);
+ return maker;
+ });
+});
+
+g.test('queue,writeBuffer').
+desc(
+ `
+Tests writeBuffer on queue on destroyed device.
+ `
+).
+params((u) => u.combine('numElements', [4, 8, 16]).combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { numElements, awaitLost } = t.params;
+ const buffer = t.device.createBuffer({
+ size: numElements,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ const data = new Uint8Array(numElements);
+ await t.executeAfterDestroy(() => {
+ t.device.queue.writeBuffer(buffer, 0, data);
+ }, awaitLost);
+});
+
+g.test('queue,writeTexture,2d,uncompressed_format').
+desc(
+ `
+Tests writeTexture on queue on destroyed device with uncompressed formats.
+ `
+).
+params((u) => u.combine('format', kRegularTextureFormats).combine('awaitLost', [true, false])).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn(async (t) => {
+ const { format, awaitLost } = t.params;
+ const {
+ blockWidth,
+ blockHeight,
+ color: { bytes: bytesPerBlock }
+ } = kTextureFormatInfo[format];
+ const data = new Uint8Array(bytesPerBlock);
+ const texture = t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: GPUTextureUsage.COPY_DST,
+ format
+ });
+ await t.executeAfterDestroy(() => {
+ t.device.queue.writeTexture(
+ { texture },
+ data,
+ {},
+ { width: blockWidth, height: blockHeight }
+ );
+ }, awaitLost);
+});
+
+g.test('queue,writeTexture,2d,compressed_format').
+desc(
+ `
+Tests writeTexture on queue on destroyed device with compressed formats.
+ `
+).
+params((u) =>
+u.
+combine('format', kCompressedTextureFormats).
+
+combine('awaitLost', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn(async (t) => {
+ const { format, awaitLost } = t.params;
+ const {
+ blockWidth,
+ blockHeight,
+ color: { bytes: bytesPerBlock }
+ } = kTextureFormatInfo[format];
+ const data = new Uint8Array(bytesPerBlock);
+ const texture = t.device.createTexture({
+ size: { width: blockWidth, height: blockHeight },
+ usage: GPUTextureUsage.COPY_DST,
+ format
+ });
+ await t.executeAfterDestroy(() => {
+ t.device.queue.writeTexture(
+ { texture },
+ data,
+ {},
+ { width: blockWidth, height: blockHeight }
+ );
+ }, awaitLost);
+});
+
+g.test('queue,copyExternalImageToTexture,canvas').
+desc(
+ `
+Tests copyExternalImageToTexture from canvas on queue on destroyed device.
+ `
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+combine('contextType', kValidCanvasContextIds).
+
+combine('awaitLost', [true, false])
+).
+fn(async (t) => {
+ const { canvasType, contextType, awaitLost } = t.params;
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ const texture = t.device.createTexture({
+ size: { width: 1, height: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ const ctx = canvas.getContext(contextType);
+ if (ctx === null) {
+ t.skip('Failed to get context for canvas element');
+ return;
+ }
+ t.tryTrackForCleanup(ctx);
+
+ await t.executeAfterDestroy(() => {
+ t.device.queue.copyExternalImageToTexture(
+ { source: canvas },
+ { texture },
+ { width: 1, height: 1 }
+ );
+ }, awaitLost);
+});
+
+g.test('queue,copyExternalImageToTexture,imageBitmap').
+desc(
+ `
+Tests copyExternalImageToTexture from canvas on queue on destroyed device.
+ `
+).
+params((u) => u.combine('awaitLost', [true, false])).
+fn(async (t) => {
+ const { awaitLost } = t.params;
+ if (typeof createImageBitmap === 'undefined') {
+ t.skip('Creating ImageBitmaps is not supported.');
+ }
+ const imageBitmap = await createImageBitmap(new ImageData(new Uint8ClampedArray(4), 1, 1));
+
+ const texture = t.device.createTexture({
+ size: { width: 1, height: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST
+ });
+
+ await t.executeAfterDestroy(() => {
+ t.device.queue.copyExternalImageToTexture(
+ { source: imageBitmap },
+ { texture },
+ { width: 1, height: 1 }
+ );
+ }, awaitLost);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/bgra8unorm_storage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/bgra8unorm_storage.spec.js
new file mode 100644
index 0000000000..57e1c5a23e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/bgra8unorm_storage.spec.js
@@ -0,0 +1,205 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for capabilities added by bgra8unorm-storage flag.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import { kTextureUsages } from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import { kAllCanvasTypes, createCanvas } from '../../../util/create_elements.js';
+import { ValidationTest } from '../validation_test.js';
+
+class BGRA8UnormStorageValidationTests extends ValidationTest {
+ testCreateShaderModuleWithBGRA8UnormStorage(
+ shaderType,
+ success)
+ {
+ let shaderDeclaration = '';
+ switch (shaderType) {
+ case 'fragment':
+ shaderDeclaration = '@fragment';
+ break;
+ case 'compute':
+ shaderDeclaration = '@compute @workgroup_size(1)';
+ break;
+ }
+ this.expectValidationError(() => {
+ this.device.createShaderModule({
+ code: `
+ @group(0) @binding(1) var outputTex: texture_storage_2d<bgra8unorm, write>;
+ ${shaderDeclaration} fn main() {
+ textureStore(outputTex, vec2<i32>(), vec4<f32>());
+ }
+ `
+ });
+ }, !success);
+ }
+}
+
+export const g = makeTestGroup(BGRA8UnormStorageValidationTests);
+
+g.test('create_texture').
+desc(
+ `
+Test that it is valid to create bgra8unorm texture with STORAGE usage iff the feature
+bgra8unorm-storage is enabled. Note, the createTexture test suite covers the validation cases where
+this feature is not enabled, which are skipped here.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
+}).
+fn((t) => {
+ const descriptor = {
+ size: [1, 1, 1],
+ format: 'bgra8unorm',
+ usage: GPUConst.TextureUsage.STORAGE
+ };
+ t.device.createTexture(descriptor);
+});
+
+g.test('create_bind_group_layout').
+desc(
+ `
+Test that it is valid to create GPUBindGroupLayout that uses bgra8unorm as storage texture format
+iff the feature bgra8unorm-storage is enabled. Note, the createBindGroupLayout test suite covers the
+validation cases where this feature is not enabled, which are skipped here.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
+}).
+fn((t) => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: { format: 'bgra8unorm' }
+ }]
+
+ });
+});
+
+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'])).
+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'])).
+fn((t) => {
+ const { shaderType } = t.params;
+
+ t.testCreateShaderModuleWithBGRA8UnormStorage(shaderType, false);
+});
+
+g.test('configure_storage_usage_on_canvas_context_without_bgra8unorm_storage').
+desc(
+ `
+Test that it is invalid to configure a GPUCanvasContext to 'GPUStorageBinding' usage with
+'bgra8unorm' format on a GPUDevice with 'bgra8unorm-storage' disabled.
+`
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+beginSubcases().
+expand('usage', () => {
+ const usageSet = new Set();
+ for (const usage0 of kTextureUsages) {
+ for (const usage1 of kTextureUsages) {
+ usageSet.add(usage0 | usage1);
+ }
+ }
+ return usageSet;
+})
+).
+fn((t) => {
+ const { canvasType, usage } = t.params;
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ const requiredStorageBinding = !!(usage & GPUTextureUsage.STORAGE_BINDING);
+ t.expectValidationError(() => {
+ ctx.configure({
+ device: t.device,
+ format: 'bgra8unorm',
+ usage
+ });
+ }, requiredStorageBinding);
+});
+
+g.test('configure_storage_usage_on_canvas_context_with_bgra8unorm_storage').
+desc(
+ `
+Test that it is valid to configure a GPUCanvasContext with GPUStorageBinding usage and a GPUDevice
+with 'bgra8unorm-storage' enabled.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
+}).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+beginSubcases().
+expand('usage', () => {
+ const usageSet = new Set();
+ for (const usage of kTextureUsages) {
+ usageSet.add(usage | GPUConst.TextureUsage.STORAGE_BINDING);
+ }
+ return usageSet;
+})
+).
+fn((t) => {
+ const { canvasType, usage } = t.params;
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: t.device,
+ format: 'bgra8unorm',
+ usage
+ });
+
+ const currentTexture = ctx.getCurrentTexture();
+ const bindGroupLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ storageTexture: { access: 'write-only', format: currentTexture.format }
+ }]
+
+ });
+ t.device.createBindGroup({
+ layout: bindGroupLayout,
+ entries: [
+ {
+ binding: 0,
+ resource: currentTexture.createView()
+ }]
+
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/destroy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/destroy.spec.js
new file mode 100644
index 0000000000..7f17d03683
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/destroy.spec.js
@@ -0,0 +1,139 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Destroying a texture more than once is allowed.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureAspects } from '../../../capability_info.js';
+import { kTextureFormatInfo } from '../../../format_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('base').
+desc(`Test that it is valid to destroy a texture.`).
+fn((t) => {
+ const texture = t.getSampledTexture();
+ texture.destroy();
+});
+
+g.test('twice').
+desc(`Test that it is valid to destroy a destroyed texture.`).
+fn((t) => {
+ const texture = t.getSampledTexture();
+ texture.destroy();
+ texture.destroy();
+});
+
+g.test('invalid_texture').
+desc('Test that invalid textures may be destroyed without generating validation errors.').
+fn(async (t) => {
+ t.device.pushErrorScope('validation');
+
+ const invalidTexture = t.device.createTexture({
+ size: [t.device.limits.maxTextureDimension2D + 1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ // Expect error because it's invalid.
+ const error = await t.device.popErrorScope();
+ t.expect(!!error);
+
+ // This line should not generate an error
+ invalidTexture.destroy();
+});
+
+g.test('submit_a_destroyed_texture_as_attachment').
+desc(
+ `
+Test that it is invalid to submit with a texture as {color, depth, stencil, depth-stencil} attachment
+that was destroyed {before, after} encoding finishes.
+`
+).
+params((u) =>
+u //
+.combine('depthStencilTextureAspect', kTextureAspects).
+combine('colorTextureState', [
+'valid',
+'destroyedBeforeEncode',
+'destroyedAfterEncode']
+).
+combine('depthStencilTextureState', [
+'valid',
+'destroyedBeforeEncode',
+'destroyedAfterEncode']
+)
+).
+fn((t) => {
+ const { colorTextureState, depthStencilTextureAspect, depthStencilTextureState } = t.params;
+
+ const isSubmitSuccess = colorTextureState === 'valid' && depthStencilTextureState === 'valid';
+
+ const colorTextureFormat = 'rgba32float';
+ const depthStencilTextureFormat =
+ depthStencilTextureAspect === 'all' ?
+ 'depth24plus-stencil8' :
+ depthStencilTextureAspect === 'depth-only' ?
+ 'depth32float' :
+ 'stencil8';
+
+ const colorTextureDesc = {
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: colorTextureFormat,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ };
+
+ const depthStencilTextureDesc = {
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: depthStencilTextureFormat,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ };
+
+ const colorTexture = t.device.createTexture(colorTextureDesc);
+ const depthStencilTexture = t.device.createTexture(depthStencilTextureDesc);
+
+ if (colorTextureState === 'destroyedBeforeEncode') {
+ colorTexture.destroy();
+ }
+ if (depthStencilTextureState === 'destroyedBeforeEncode') {
+ depthStencilTexture.destroy();
+ }
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const depthStencilAttachment = {
+ view: depthStencilTexture.createView({ aspect: depthStencilTextureAspect })
+ };
+ if (kTextureFormatInfo[depthStencilTextureFormat].depth) {
+ depthStencilAttachment.depthClearValue = 0;
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'discard';
+ }
+ if (kTextureFormatInfo[depthStencilTextureFormat].stencil) {
+ depthStencilAttachment.stencilClearValue = 0;
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'discard';
+ }
+ const renderPass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorTexture.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }],
+
+ depthStencilAttachment
+ });
+ renderPass.end();
+
+ const cmd = commandEncoder.finish();
+
+ if (colorTextureState === 'destroyedAfterEncode') {
+ colorTexture.destroy();
+ }
+ if (depthStencilTextureState === 'destroyedAfterEncode') {
+ depthStencilTexture.destroy();
+ }
+
+ t.expectValidationError(() => t.queue.submit([cmd]), !isSubmitSuccess);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/float32_filterable.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/float32_filterable.spec.js
new file mode 100644
index 0000000000..05575673f9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/float32_filterable.spec.js
@@ -0,0 +1,58 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for capabilities added by float32-filterable flag.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureSampleTypes } from '../../../capability_info.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+const kFloat32Formats = ['r32float', 'rg32float', 'rgba32float'];
+
+g.test('create_bind_group').
+desc(
+ `
+Test that it is valid to bind a float32 texture format to a 'float' sampled texture iff
+float32-filterable is enabled.
+`
+).
+params((u) =>
+u.
+combine('enabled', [true, false]).
+beginSubcases().
+combine('format', kFloat32Formats).
+combine('sampleType', kTextureSampleTypes)
+).
+beforeAllSubcases((t) => {
+ if (t.params.enabled) {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+}).
+fn((t) => {
+ const { enabled, format, sampleType } = t.params;
+ const layout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: { sampleType }
+ }]
+
+ });
+ const textureDesc = {
+ size: { width: 4, height: 4 },
+ format,
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ };
+ const shouldError = !(
+ enabled && sampleType === 'float' ||
+ sampleType === 'unfilterable-float');
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ entries: [{ binding: 0, resource: t.device.createTexture(textureDesc).createView() }],
+ layout
+ });
+ }, shouldError);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/rg11b10ufloat_renderable.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/rg11b10ufloat_renderable.spec.js
new file mode 100644
index 0000000000..29e796d618
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/texture/rg11b10ufloat_renderable.spec.js
@@ -0,0 +1,149 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for capabilities added by rg11b10ufloat-renderable flag.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUConst } from '../../../constants.js';
+import { ValidationTest } from '../validation_test.js';
+
+export const g = makeTestGroup(ValidationTest);
+
+g.test('create_texture').
+desc(
+ `
+Test that it is valid to create rg11b10ufloat texture with RENDER_ATTACHMENT usage and/or
+sampleCount > 1, iff rg11b10ufloat-renderable feature is enabled.
+Note, the createTexture tests cover these validation cases where this feature is not enabled.
+`
+).
+params((u) => u.combine('sampleCount', [1, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('rg11b10ufloat-renderable');
+}).
+fn((t) => {
+ const { sampleCount } = t.params;
+ const descriptor = {
+ size: [1, 1, 1],
+ format: 'rg11b10ufloat',
+ sampleCount,
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT
+ };
+ t.device.createTexture(descriptor);
+});
+
+g.test('begin_render_pass_single_sampled').
+desc(
+ `
+Test that it is valid to begin render pass with rg11b10ufloat texture format
+iff rg11b10ufloat-renderable feature is enabled. Single sampled case.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('rg11b10ufloat-renderable');
+}).
+fn((t) => {
+ const texture = t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rg11b10ufloat',
+ sampleCount: 1,
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT
+ });
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ encoder.finish();
+});
+
+g.test('begin_render_pass_msaa_and_resolve').
+desc(
+ `
+Test that it is valid to begin render pass with rg11b10ufloat texture format
+iff rg11b10ufloat-renderable feature is enabled. MSAA and resolve case.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('rg11b10ufloat-renderable');
+}).
+fn((t) => {
+ const renderTexture = t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rg11b10ufloat',
+ sampleCount: 4,
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT
+ });
+ const resolveTexture = t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rg11b10ufloat',
+ sampleCount: 1,
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT
+ });
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTexture.createView(),
+ resolveTarget: resolveTexture.createView(),
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ encoder.finish();
+});
+
+g.test('begin_render_bundle_encoder').
+desc(
+ `
+Test that it is valid to begin render bundle encoder with rg11b10ufloat texture
+format iff rg11b10ufloat-renderable feature is enabled.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('rg11b10ufloat-renderable');
+}).
+fn((t) => {
+ t.device.createRenderBundleEncoder({
+ colorFormats: ['rg11b10ufloat']
+ });
+});
+
+g.test('create_render_pipeline').
+desc(
+ `
+Test that it is valid to create render pipeline with rg11b10ufloat texture format
+in descriptor.fragment.targets iff rg11b10ufloat-renderable feature is enabled.
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('rg11b10ufloat-renderable');
+}).
+fn((t) => {
+ t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: t.getNoOpShaderCode('VERTEX')
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: t.getNoOpShaderCode('FRAGMENT')
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rg11b10ufloat', writeMask: 0 }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/validation_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/validation_test.js
new file mode 100644
index 0000000000..9ddb6d9fad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/validation_test.js
@@ -0,0 +1,448 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import {
+ kMaxQueryCount } from
+
+'../../capability_info.js';
+import { GPUTest } from '../../gpu_test.js';
+
+/**
+ * Base fixture for WebGPU validation tests.
+ */
+export class ValidationTest extends GPUTest {
+ /**
+ * Create a GPUTexture in the specified state.
+ * A `descriptor` may optionally be passed, which is used when `state` is not `'invalid'`.
+ */
+ createTextureWithState(
+ state,
+ descriptor)
+ {
+ descriptor = descriptor ?? {
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.STORAGE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT
+ };
+
+ switch (state) {
+ case 'valid':
+ return this.trackForCleanup(this.device.createTexture(descriptor));
+ case 'invalid':
+ return this.getErrorTexture();
+ case 'destroyed':{
+ const texture = this.device.createTexture(descriptor);
+ texture.destroy();
+ return texture;
+ }
+ }
+ }
+
+ /**
+ * Create a GPUTexture in the specified state. A `descriptor` may optionally be passed;
+ * if `state` is `'invalid'`, it will be modified to add an invalid combination of usages.
+ */
+ createBufferWithState(
+ state,
+ descriptor)
+ {
+ descriptor = descriptor ?? {
+ size: 4,
+ usage: GPUBufferUsage.VERTEX
+ };
+
+ switch (state) {
+ case 'valid':
+ return this.trackForCleanup(this.device.createBuffer(descriptor));
+
+ case 'invalid':{
+ // Make the buffer invalid because of an invalid combination of usages but keep the
+ // descriptor passed as much as possible (for mappedAtCreation and friends).
+ this.device.pushErrorScope('validation');
+ const buffer = this.device.createBuffer({
+ ...descriptor,
+ usage: descriptor.usage | GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_SRC
+ });
+ void this.device.popErrorScope();
+ return buffer;
+ }
+ case 'destroyed':{
+ const buffer = this.device.createBuffer(descriptor);
+ buffer.destroy();
+ return buffer;
+ }
+ }
+ }
+
+ /**
+ * Create a GPUQuerySet in the specified state.
+ * A `descriptor` may optionally be passed, which is used when `state` is not `'invalid'`.
+ */
+ createQuerySetWithState(
+ state,
+ desc)
+ {
+ const descriptor = { type: 'occlusion', count: 2, ...desc };
+
+ switch (state) {
+ case 'valid':
+ return this.trackForCleanup(this.device.createQuerySet(descriptor));
+ case 'invalid':{
+ // Make the queryset invalid because of the count out of bounds.
+ descriptor.count = kMaxQueryCount + 1;
+ return this.expectGPUError('validation', () => this.device.createQuerySet(descriptor));
+ }
+ case 'destroyed':{
+ const queryset = this.device.createQuerySet(descriptor);
+ queryset.destroy();
+ return queryset;
+ }
+ }
+ }
+
+ /** Create an arbitrarily-sized GPUBuffer with the STORAGE usage. */
+ getStorageBuffer() {
+ return this.trackForCleanup(
+ this.device.createBuffer({ size: 1024, usage: GPUBufferUsage.STORAGE })
+ );
+ }
+
+ /** Create an arbitrarily-sized GPUBuffer with the UNIFORM usage. */
+ getUniformBuffer() {
+ return this.trackForCleanup(
+ this.device.createBuffer({ size: 1024, usage: GPUBufferUsage.UNIFORM })
+ );
+ }
+
+ /** Return an invalid GPUBuffer. */
+ getErrorBuffer() {
+ return this.createBufferWithState('invalid');
+ }
+
+ /** Return an invalid GPUSampler. */
+ getErrorSampler() {
+ this.device.pushErrorScope('validation');
+ const sampler = this.device.createSampler({ lodMinClamp: -1 });
+ void this.device.popErrorScope();
+ return sampler;
+ }
+
+ /**
+ * Return an arbitrarily-configured GPUTexture with the `TEXTURE_BINDING` usage and specified
+ * sampleCount. The `RENDER_ATTACHMENT` usage will also be specified if sampleCount > 1 as is
+ * required by WebGPU SPEC.
+ */
+ getSampledTexture(sampleCount = 1) {
+ const usage =
+ sampleCount > 1 ?
+ GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT :
+ GPUTextureUsage.TEXTURE_BINDING;
+ return this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage,
+ sampleCount
+ })
+ );
+ }
+
+ /** Return an arbitrarily-configured GPUTexture with the `STORAGE_BINDING` usage. */
+ getStorageTexture() {
+ return this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.STORAGE_BINDING
+ })
+ );
+ }
+
+ /** Return an arbitrarily-configured GPUTexture with the `RENDER_ATTACHMENT` usage. */
+ getRenderTexture(sampleCount = 1) {
+ return this.trackForCleanup(
+ this.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount
+ })
+ );
+ }
+
+ /** Return an invalid GPUTexture. */
+ getErrorTexture() {
+ this.device.pushErrorScope('validation');
+ const texture = this.device.createTexture({
+ size: { width: 0, height: 0, depthOrArrayLayers: 0 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+ void this.device.popErrorScope();
+ return texture;
+ }
+
+ /** Return an invalid GPUTextureView (created from an invalid GPUTexture). */
+ getErrorTextureView() {
+ this.device.pushErrorScope('validation');
+ const view = this.getErrorTexture().createView();
+ void this.device.popErrorScope();
+ return view;
+ }
+
+ /**
+ * Return an arbitrary object of the specified {@link webgpu/capability_info!BindableResource} type
+ * (e.g. `'errorBuf'`, `'nonFiltSamp'`, `sampledTexMS`, etc.)
+ */
+ getBindingResource(bindingType) {
+ switch (bindingType) {
+ case 'errorBuf':
+ return { buffer: this.getErrorBuffer() };
+ case 'errorSamp':
+ return this.getErrorSampler();
+ case 'errorTex':
+ return this.getErrorTextureView();
+ case 'uniformBuf':
+ return { buffer: this.getUniformBuffer() };
+ case 'storageBuf':
+ return { buffer: this.getStorageBuffer() };
+ case 'filtSamp':
+ return this.device.createSampler({ minFilter: 'linear' });
+ case 'nonFiltSamp':
+ return this.device.createSampler();
+ case 'compareSamp':
+ return this.device.createSampler({ compare: 'never' });
+ case 'sampledTex':
+ return this.getSampledTexture(1).createView();
+ case 'sampledTexMS':
+ return this.getSampledTexture(4).createView();
+ case 'storageTex':
+ return this.getStorageTexture().createView();
+ }
+ }
+
+ /** Create an arbitrarily-sized GPUBuffer with the STORAGE usage from mismatched device. */
+ getDeviceMismatchedStorageBuffer() {
+ return this.trackForCleanup(
+ this.mismatchedDevice.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE })
+ );
+ }
+
+ /** Create an arbitrarily-sized GPUBuffer with the UNIFORM usage from mismatched device. */
+ getDeviceMismatchedUniformBuffer() {
+ return this.trackForCleanup(
+ this.mismatchedDevice.createBuffer({ size: 4, usage: GPUBufferUsage.UNIFORM })
+ );
+ }
+
+ /** Return a GPUTexture with descriptor from mismatched device. */
+ getDeviceMismatchedTexture(descriptor) {
+ return this.trackForCleanup(this.mismatchedDevice.createTexture(descriptor));
+ }
+
+ /** Return an arbitrarily-configured GPUTexture with the `SAMPLED` usage from mismatched device. */
+ getDeviceMismatchedSampledTexture(sampleCount = 1) {
+ return this.getDeviceMismatchedTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ sampleCount
+ });
+ }
+
+ /** Return an arbitrarily-configured GPUTexture with the `STORAGE` usage from mismatched device. */
+ getDeviceMismatchedStorageTexture() {
+ return this.getDeviceMismatchedTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.STORAGE_BINDING
+ });
+ }
+
+ /** Return an arbitrarily-configured GPUTexture with the `RENDER_ATTACHMENT` usage from mismatched device. */
+ getDeviceMismatchedRenderTexture(sampleCount = 1) {
+ return this.getDeviceMismatchedTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount
+ });
+ }
+
+ getDeviceMismatchedBindingResource(bindingType) {
+ switch (bindingType) {
+ case 'uniformBuf':
+ return { buffer: this.getDeviceMismatchedStorageBuffer() };
+ case 'storageBuf':
+ return { buffer: this.getDeviceMismatchedUniformBuffer() };
+ case 'filtSamp':
+ return this.mismatchedDevice.createSampler({ minFilter: 'linear' });
+ case 'nonFiltSamp':
+ return this.mismatchedDevice.createSampler();
+ case 'compareSamp':
+ return this.mismatchedDevice.createSampler({ compare: 'never' });
+ case 'sampledTex':
+ return this.getDeviceMismatchedSampledTexture(1).createView();
+ case 'sampledTexMS':
+ return this.getDeviceMismatchedSampledTexture(4).createView();
+ case 'storageTex':
+ return this.getDeviceMismatchedStorageTexture().createView();
+ }
+ }
+
+ /** Return a no-op shader code snippet for the specified shader stage. */
+ getNoOpShaderCode(stage) {
+ switch (stage) {
+ case 'VERTEX':
+ return `
+ @vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+ }
+ `;
+ case 'FRAGMENT':
+ return `@fragment fn main() {}`;
+ case 'COMPUTE':
+ return `@compute @workgroup_size(1) fn main() {}`;
+ }
+ }
+
+ /** Create a GPURenderPipeline in the specified state. */
+ createRenderPipelineWithState(state) {
+ return state === 'valid' ? this.createNoOpRenderPipeline() : this.createErrorRenderPipeline();
+ }
+
+ /** Return a GPURenderPipeline with default options and no-op vertex and fragment shaders. */
+ createNoOpRenderPipeline(
+ layout = 'auto')
+ {
+ return this.device.createRenderPipeline({
+ layout,
+ vertex: {
+ module: this.device.createShaderModule({
+ code: this.getNoOpShaderCode('VERTEX')
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: this.getNoOpShaderCode('FRAGMENT')
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm', writeMask: 0 }]
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+ }
+
+ /** Return an invalid GPURenderPipeline. */
+ createErrorRenderPipeline() {
+ this.device.pushErrorScope('validation');
+ const pipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: ''
+ }),
+ entryPoint: ''
+ }
+ });
+ void this.device.popErrorScope();
+ return pipeline;
+ }
+
+ /** Return a GPUComputePipeline with a no-op shader. */
+ createNoOpComputePipeline(
+ layout = 'auto')
+ {
+ return this.device.createComputePipeline({
+ layout,
+ compute: {
+ module: this.device.createShaderModule({
+ code: this.getNoOpShaderCode('COMPUTE')
+ }),
+ entryPoint: 'main'
+ }
+ });
+ }
+
+ /** Return an invalid GPUComputePipeline. */
+ createErrorComputePipeline() {
+ this.device.pushErrorScope('validation');
+ const pipeline = this.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: this.device.createShaderModule({
+ code: ''
+ }),
+ entryPoint: ''
+ }
+ });
+ void this.device.popErrorScope();
+ return pipeline;
+ }
+
+ /** Return an invalid GPUShaderModule. */
+ createInvalidShaderModule() {
+ this.device.pushErrorScope('validation');
+ const code = 'deadbeaf'; // Something make no sense
+ const shaderModule = this.device.createShaderModule({ code });
+ void this.device.popErrorScope();
+ return shaderModule;
+ }
+
+ /** Helper for testing createRenderPipeline(Async) validation */
+ doCreateRenderPipelineTest(
+ isAsync,
+ _success,
+ descriptor,
+ errorTypeName = 'GPUPipelineError')
+ {
+ if (isAsync) {
+ if (_success) {
+ this.shouldResolve(this.device.createRenderPipelineAsync(descriptor));
+ } else {
+ this.shouldReject(errorTypeName, this.device.createRenderPipelineAsync(descriptor));
+ }
+ } else {
+ if (errorTypeName === 'GPUPipelineError') {
+ this.expectValidationError(() => {
+ this.device.createRenderPipeline(descriptor);
+ }, !_success);
+ } else {
+ this.shouldThrow(_success ? false : errorTypeName, () => {
+ this.device.createRenderPipeline(descriptor);
+ });
+ }
+ }
+ }
+
+ /** Helper for testing createComputePipeline(Async) validation */
+ doCreateComputePipelineTest(
+ isAsync,
+ _success,
+ descriptor,
+ errorTypeName = 'GPUPipelineError')
+ {
+ if (isAsync) {
+ if (_success) {
+ this.shouldResolve(this.device.createComputePipelineAsync(descriptor));
+ } else {
+ this.shouldReject(errorTypeName, this.device.createComputePipelineAsync(descriptor));
+ }
+ } else {
+ if (errorTypeName === 'GPUPipelineError') {
+ this.expectValidationError(() => {
+ this.device.createComputePipeline(descriptor);
+ }, !_success);
+ } else {
+ this.shouldThrow(_success ? false : errorTypeName, () => {
+ this.device.createComputePipeline(descriptor);
+ });
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/capability_info.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/capability_info.js
new file mode 100644
index 0000000000..dbcb833947
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/capability_info.js
@@ -0,0 +1,792 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // MAINTENANCE_TODO: The generated Typedoc for this file is hard to navigate because it's
+// alphabetized. Consider using namespaces or renames to fix this?
+
+import {
+ keysOf,
+ makeTable,
+ makeTableRenameAndFilter,
+ numericKeysOf } from
+
+'../common/util/data_tables.js';
+import { assertTypeTrue } from '../common/util/types.js';
+import { unreachable } from '../common/util/util.js';
+
+import { GPUConst, kMaxUnsignedLongValue, kMaxUnsignedLongLongValue } from './constants.js';
+
+// Base device limits can be found in constants.ts.
+
+// Queries
+
+/** Maximum number of queries in GPUQuerySet, by spec. */
+export const kMaxQueryCount = 4096;
+/** Per-GPUQueryType info. */
+
+
+
+
+
+export const kQueryTypeInfo =
+
+
+{
+ 'occlusion': { feature: undefined },
+ 'timestamp': { feature: 'timestamp-query' }
+};
+/** List of all GPUQueryType values. */
+export const kQueryTypes = keysOf(kQueryTypeInfo);
+
+// Buffers
+
+/** Required alignment of a GPUBuffer size, by spec. */
+export const kBufferSizeAlignment = 4;
+
+/** Per-GPUBufferUsage copy info. */
+export const kBufferUsageCopyInfo =
+
+
+{
+ 'COPY_NONE': 0,
+ 'COPY_SRC': GPUConst.BufferUsage.COPY_SRC,
+ 'COPY_DST': GPUConst.BufferUsage.COPY_DST,
+ 'COPY_SRC_DST': GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.COPY_DST
+};
+/** List of all GPUBufferUsage copy values. */
+export const kBufferUsageCopy = keysOf(kBufferUsageCopyInfo);
+
+/** Per-GPUBufferUsage keys and info. */
+
+export const kBufferUsageKeys = keysOf(GPUConst.BufferUsage);
+export const kBufferUsageInfo =
+
+{
+ ...GPUConst.BufferUsage
+};
+
+/** List of all GPUBufferUsage values. */
+export const kBufferUsages = Object.values(GPUConst.BufferUsage);
+export const kAllBufferUsageBits = kBufferUsages.reduce(
+ (previousSet, currentUsage) => previousSet | currentUsage,
+ 0
+);
+
+// Errors
+
+/** Per-GPUErrorFilter info. */
+export const kErrorScopeFilterInfo =
+
+
+
+
+{
+ 'internal': { generatable: false },
+ 'out-of-memory': { generatable: true },
+ 'validation': { generatable: true }
+};
+/** List of all GPUErrorFilter values. */
+export const kErrorScopeFilters = keysOf(kErrorScopeFilterInfo);
+export const kGeneratableErrorScopeFilters = kErrorScopeFilters.filter(
+ (e) => kErrorScopeFilterInfo[e].generatable
+);
+
+// Canvases
+
+// The formats of GPUTextureFormat for canvas context.
+export const kCanvasTextureFormats = ['bgra8unorm', 'rgba8unorm', 'rgba16float'];
+
+// The alpha mode for canvas context.
+export const kCanvasAlphaModesInfo =
+
+{
+ 'opaque': {},
+ 'premultiplied': {}
+};
+export const kCanvasAlphaModes = keysOf(kCanvasAlphaModesInfo);
+
+// The color spaces for canvas context
+export const kCanvasColorSpacesInfo =
+
+{
+ 'srgb': {},
+ 'display-p3': {}
+};
+export const kCanvasColorSpaces = keysOf(kCanvasColorSpacesInfo);
+
+// Textures (except for texture format info)
+
+/** Per-GPUTextureDimension info. */
+export const kTextureDimensionInfo =
+
+{
+ '1d': {},
+ '2d': {},
+ '3d': {}
+};
+/** List of all GPUTextureDimension values. */
+export const kTextureDimensions = keysOf(kTextureDimensionInfo);
+
+/** Per-GPUTextureAspect info. */
+export const kTextureAspectInfo =
+
+{
+ 'all': {},
+ 'depth-only': {},
+ 'stencil-only': {}
+};
+/** List of all GPUTextureAspect values. */
+export const kTextureAspects = keysOf(kTextureAspectInfo);
+
+// Misc
+
+/** Per-GPUCompareFunction info. */
+export const kCompareFunctionInfo =
+
+
+{
+ 'never': {},
+ 'less': {},
+ 'equal': {},
+ 'less-equal': {},
+ 'greater': {},
+ 'not-equal': {},
+ 'greater-equal': {},
+ 'always': {}
+};
+/** List of all GPUCompareFunction values. */
+export const kCompareFunctions = keysOf(kCompareFunctionInfo);
+
+/** Per-GPUStencilOperation info. */
+export const kStencilOperationInfo =
+
+
+{
+ 'keep': {},
+ 'zero': {},
+ 'replace': {},
+ 'invert': {},
+ 'increment-clamp': {},
+ 'decrement-clamp': {},
+ 'increment-wrap': {},
+ 'decrement-wrap': {}
+};
+/** List of all GPUStencilOperation values. */
+export const kStencilOperations = keysOf(kStencilOperationInfo);
+
+// More textures (except for texture format info)
+
+/** Per-GPUTextureUsage type info. */
+export const kTextureUsageTypeInfo =
+
+
+{
+ 'texture': Number(GPUConst.TextureUsage.TEXTURE_BINDING),
+ 'storage': Number(GPUConst.TextureUsage.STORAGE_BINDING),
+ 'render': Number(GPUConst.TextureUsage.RENDER_ATTACHMENT)
+};
+/** List of all GPUTextureUsage type values. */
+export const kTextureUsageType = keysOf(kTextureUsageTypeInfo);
+
+/** Per-GPUTextureUsage copy info. */
+export const kTextureUsageCopyInfo =
+
+
+{
+ 'none': 0,
+ 'src': Number(GPUConst.TextureUsage.COPY_SRC),
+ 'dst': Number(GPUConst.TextureUsage.COPY_DST),
+ 'src-dest': Number(GPUConst.TextureUsage.COPY_SRC) | Number(GPUConst.TextureUsage.COPY_DST)
+};
+/** List of all GPUTextureUsage copy values. */
+export const kTextureUsageCopy = keysOf(kTextureUsageCopyInfo);
+
+/** Per-GPUTextureUsage info. */
+export const kTextureUsageInfo =
+
+{
+ [GPUConst.TextureUsage.COPY_SRC]: {},
+ [GPUConst.TextureUsage.COPY_DST]: {},
+ [GPUConst.TextureUsage.TEXTURE_BINDING]: {},
+ [GPUConst.TextureUsage.STORAGE_BINDING]: {},
+ [GPUConst.TextureUsage.RENDER_ATTACHMENT]: {}
+};
+/** List of all GPUTextureUsage values. */
+export const kTextureUsages = numericKeysOf(kTextureUsageInfo);
+
+// Texture View
+
+/** Per-GPUTextureViewDimension info. */
+
+
+
+
+
+/** Per-GPUTextureViewDimension info. */
+export const kTextureViewDimensionInfo =
+
+
+{
+ '1d': { storage: true },
+ '2d': { storage: true },
+ '2d-array': { storage: true },
+ 'cube': { storage: false },
+ 'cube-array': { storage: false },
+ '3d': { storage: true }
+};
+/** List of all GPUTextureDimension values. */
+export const kTextureViewDimensions = keysOf(kTextureViewDimensionInfo);
+
+// Vertex formats
+
+/** Per-GPUVertexFormat info. */
+// Exists just for documentation. Otherwise could be inferred by `makeTable`.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** Per-GPUVertexFormat info. */
+export const kVertexFormatInfo =
+
+
+makeTable(
+ ['bytesPerComponent', 'type', 'componentCount', 'byteSize', 'wgslType'],
+ [,,,,], {
+ // 8 bit components
+ 'uint8x2': [1, 'uint', 2, 2, 'vec2<u32>'],
+ 'uint8x4': [1, 'uint', 4, 4, 'vec4<u32>'],
+ 'sint8x2': [1, 'sint', 2, 2, 'vec2<i32>'],
+ 'sint8x4': [1, 'sint', 4, 4, 'vec4<i32>'],
+ 'unorm8x2': [1, 'unorm', 2, 2, 'vec2<f32>'],
+ 'unorm8x4': [1, 'unorm', 4, 4, 'vec4<f32>'],
+ 'snorm8x2': [1, 'snorm', 2, 2, 'vec2<f32>'],
+ 'snorm8x4': [1, 'snorm', 4, 4, 'vec4<f32>'],
+ // 16 bit components
+ 'uint16x2': [2, 'uint', 2, 4, 'vec2<u32>'],
+ 'uint16x4': [2, 'uint', 4, 8, 'vec4<u32>'],
+ 'sint16x2': [2, 'sint', 2, 4, 'vec2<i32>'],
+ 'sint16x4': [2, 'sint', 4, 8, 'vec4<i32>'],
+ 'unorm16x2': [2, 'unorm', 2, 4, 'vec2<f32>'],
+ 'unorm16x4': [2, 'unorm', 4, 8, 'vec4<f32>'],
+ 'snorm16x2': [2, 'snorm', 2, 4, 'vec2<f32>'],
+ 'snorm16x4': [2, 'snorm', 4, 8, 'vec4<f32>'],
+ 'float16x2': [2, 'float', 2, 4, 'vec2<f32>'],
+ 'float16x4': [2, 'float', 4, 8, 'vec4<f32>'],
+ // 32 bit components
+ 'float32': [4, 'float', 1, 4, 'f32'],
+ 'float32x2': [4, 'float', 2, 8, 'vec2<f32>'],
+ 'float32x3': [4, 'float', 3, 12, 'vec3<f32>'],
+ 'float32x4': [4, 'float', 4, 16, 'vec4<f32>'],
+ 'uint32': [4, 'uint', 1, 4, 'u32'],
+ 'uint32x2': [4, 'uint', 2, 8, 'vec2<u32>'],
+ 'uint32x3': [4, 'uint', 3, 12, 'vec3<u32>'],
+ 'uint32x4': [4, 'uint', 4, 16, 'vec4<u32>'],
+ 'sint32': [4, 'sint', 1, 4, 'i32'],
+ 'sint32x2': [4, 'sint', 2, 8, 'vec2<i32>'],
+ 'sint32x3': [4, 'sint', 3, 12, 'vec3<i32>'],
+ 'sint32x4': [4, 'sint', 4, 16, 'vec4<i32>'],
+ // 32 bit packed
+ 'unorm10-10-10-2': ['packed', 'unorm', 4, 4, 'vec4<f32>']
+ });
+/** List of all GPUVertexFormat values. */
+export const kVertexFormats = keysOf(kVertexFormatInfo);
+
+// Typedefs for bindings
+
+/**
+ * Classes of `PerShaderStage` binding limits. Two bindings with the same class
+ * count toward the same `PerShaderStage` limit(s) in the spec (if any).
+ */
+
+
+
+
+
+
+/**
+ * Classes of `PerPipelineLayout` binding limits. Two bindings with the same class
+ * count toward the same `PerPipelineLayout` limit(s) in the spec (if any).
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Types of resource binding which have distinct binding rules, by spec
+ * (e.g. filtering vs non-filtering sampler, multisample vs non-multisample texture).
+ */
+
+export const kBindableResources = [
+'uniformBuf',
+'storageBuf',
+'filtSamp',
+'nonFiltSamp',
+'compareSamp',
+'sampledTex',
+'sampledTexMS',
+'storageTex',
+'errorBuf',
+'errorSamp',
+'errorTex'];
+
+assertTypeTrue();
+
+// Bindings
+
+/** Dynamic buffer offsets require offset to be divisible by 256, by spec. */
+export const kMinDynamicBufferOffsetAlignment = 256;
+
+/** Default `PerShaderStage` binding limits, by spec. */
+export const kPerStageBindingLimits =
+
+
+
+
+
+
+
+
+{
+ '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' }
+};
+
+/**
+ * Default `PerPipelineLayout` binding limits, by spec.
+ */
+export const kPerPipelineBindingLimits =
+
+
+
+
+
+
+
+
+
+
+{
+ 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout' },
+ 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout' },
+ 'sampler': { class: 'sampler', maxDynamicLimit: '' },
+ 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '' },
+ 'storageTex': { class: 'storageTex', maxDynamicLimit: '' }
+};
+
+
+
+
+
+
+
+
+const kBindingKind =
+
+
+{
+ 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 }
+};
+
+// Binding type info
+
+const kValidStagesAll = {
+ validStages:
+ GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE
+};
+const kValidStagesStorageWrite = {
+ validStages: GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE
+};
+
+/** Binding type info (including class limits) for the specified GPUBufferBindingLayout. */
+export function bufferBindingTypeInfo(d) {
+
+ switch (d.type ?? 'uniform') {
+ case 'uniform':return { usage: GPUConst.BufferUsage.UNIFORM, ...kBindingKind.uniformBuf, ...kValidStagesAll };
+ case 'storage':return { usage: GPUConst.BufferUsage.STORAGE, ...kBindingKind.storageBuf, ...kValidStagesStorageWrite };
+ case 'read-only-storage':return { usage: GPUConst.BufferUsage.STORAGE, ...kBindingKind.storageBuf, ...kValidStagesAll };
+ }
+}
+/** List of all GPUBufferBindingType values. */
+export const kBufferBindingTypes = ['uniform', 'storage', 'read-only-storage'];
+assertTypeTrue();
+
+/** Binding type info (including class limits) for the specified GPUSamplerBindingLayout. */
+export function samplerBindingTypeInfo(d) {
+
+ switch (d.type ?? 'filtering') {
+ case 'filtering':return { ...kBindingKind.filtSamp, ...kValidStagesAll };
+ case 'non-filtering':return { ...kBindingKind.nonFiltSamp, ...kValidStagesAll };
+ case 'comparison':return { ...kBindingKind.compareSamp, ...kValidStagesAll };
+ }
+}
+/** List of all GPUSamplerBindingType values. */
+export const kSamplerBindingTypes = ['filtering', 'non-filtering', 'comparison'];
+assertTypeTrue();
+
+/** Binding type info (including class limits) for the specified GPUTextureBindingLayout. */
+export function sampledTextureBindingTypeInfo(d) {
+
+ if (d.multisampled) {
+ return { usage: GPUConst.TextureUsage.TEXTURE_BINDING, ...kBindingKind.sampledTexMS, ...kValidStagesAll };
+ } else {
+ return { usage: GPUConst.TextureUsage.TEXTURE_BINDING, ...kBindingKind.sampledTex, ...kValidStagesAll };
+ }
+}
+/** List of all GPUTextureSampleType values. */
+export const kTextureSampleTypes = [
+'float',
+'unfilterable-float',
+'depth',
+'sint',
+'uint'];
+
+assertTypeTrue();
+
+/** Binding type info (including class limits) for the specified GPUStorageTextureBindingLayout. */
+export function storageTextureBindingTypeInfo(d) {
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.storageTex,
+ ...kValidStagesStorageWrite
+ };
+}
+/** List of all GPUStorageTextureAccess values. */
+export const kStorageTextureAccessValues = ['write-only'];
+assertTypeTrue();
+
+/** GPUBindGroupLayoutEntry, but only the "union" fields, not the common fields. */
+
+/** Binding type info (including class limits) for the specified BGLEntry. */
+export function texBindingTypeInfo(e) {
+ if (e.texture !== undefined) return sampledTextureBindingTypeInfo(e.texture);
+ if (e.storageTexture !== undefined) return storageTextureBindingTypeInfo(e.storageTexture);
+ unreachable();
+}
+/** BindingTypeInfo (including class limits) for the specified BGLEntry. */
+export function bindingTypeInfo(e) {
+ if (e.buffer !== undefined) return bufferBindingTypeInfo(e.buffer);
+ if (e.texture !== undefined) return sampledTextureBindingTypeInfo(e.texture);
+ if (e.sampler !== undefined) return samplerBindingTypeInfo(e.sampler);
+ if (e.storageTexture !== undefined) return storageTextureBindingTypeInfo(e.storageTexture);
+ unreachable('GPUBindGroupLayoutEntry has no BindingLayout');
+}
+
+/**
+ * Generate a list of possible buffer-typed BGLEntry values.
+ *
+ * Note: Generates different `type` options, but not `hasDynamicOffset` options.
+ */
+export function bufferBindingEntries(includeUndefined) {
+ return [
+ ...(includeUndefined ? [{ buffer: { type: undefined } }] : []),
+ { buffer: { type: 'uniform' } },
+ { buffer: { type: 'storage' } },
+ { buffer: { type: 'read-only-storage' } }];
+
+}
+/** Generate a list of possible sampler-typed BGLEntry values. */
+export function samplerBindingEntries(includeUndefined) {
+ return [
+ ...(includeUndefined ? [{ sampler: { type: undefined } }] : []),
+ { sampler: { type: 'comparison' } },
+ { sampler: { type: 'filtering' } },
+ { sampler: { type: 'non-filtering' } }];
+
+}
+/**
+ * Generate a list of possible texture-typed BGLEntry values.
+ *
+ * Note: Generates different `multisampled` options, but not `sampleType` or `viewDimension` options.
+ */
+export function textureBindingEntries(includeUndefined) {
+ return [
+ ...(includeUndefined ? [{ texture: { multisampled: undefined } }] : []),
+ { texture: { multisampled: false } },
+ { texture: { multisampled: true, sampleType: 'unfilterable-float' } }];
+
+}
+/**
+ * Generate a list of possible storageTexture-typed BGLEntry values.
+ *
+ * Note: Generates different `access` options, but not `format` or `viewDimension` options.
+ */
+export function storageTextureBindingEntries(format) {
+ return [{ storageTexture: { access: 'write-only', format } }];
+}
+/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
+export function sampledAndStorageBindingEntries(
+includeUndefined,
+storageTextureFormat = 'rgba8unorm')
+{
+ return [
+ ...textureBindingEntries(includeUndefined),
+ ...storageTextureBindingEntries(storageTextureFormat)];
+
+}
+/**
+ * Generate a list of possible BGLEntry values of every type, but not variants with different:
+ * - buffer.hasDynamicOffset
+ * - texture.sampleType
+ * - texture.viewDimension
+ * - storageTexture.viewDimension
+ */
+export function allBindingEntries(
+includeUndefined,
+storageTextureFormat = 'rgba8unorm')
+{
+ return [
+ ...bufferBindingEntries(includeUndefined),
+ ...samplerBindingEntries(includeUndefined),
+ ...sampledAndStorageBindingEntries(includeUndefined, storageTextureFormat)];
+
+}
+
+// Shader stages
+
+/** List of all GPUShaderStage values. */
+
+export const kShaderStageKeys = Object.keys(GPUConst.ShaderStage);
+export const kShaderStages = [
+GPUConst.ShaderStage.VERTEX,
+GPUConst.ShaderStage.FRAGMENT,
+GPUConst.ShaderStage.COMPUTE];
+
+/** List of all possible combinations of GPUShaderStage values. */
+export const kShaderStageCombinations = [0, 1, 2, 3, 4, 5, 6, 7];
+export const kShaderStageCombinationsWithStage = [
+1, 2, 3, 4, 5, 6, 7];
+
+
+/**
+ * List of all possible texture sampleCount values.
+ *
+ * MAINTENANCE_TODO: Switch existing tests to use kTextureSampleCounts
+ */
+export const kTextureSampleCounts = [1, 4];
+
+// Sampler info
+
+/** List of all mipmap filter modes. */
+export const kMipmapFilterModes = ['nearest', 'linear'];
+assertTypeTrue();
+
+/** List of address modes. */
+export const kAddressModes = [
+'clamp-to-edge',
+'repeat',
+'mirror-repeat'];
+
+assertTypeTrue();
+
+// Blend factors and Blend components
+
+/** List of all GPUBlendFactor values. */
+export const kBlendFactors = [
+'zero',
+'one',
+'src',
+'one-minus-src',
+'src-alpha',
+'one-minus-src-alpha',
+'dst',
+'one-minus-dst',
+'dst-alpha',
+'one-minus-dst-alpha',
+'src-alpha-saturated',
+'constant',
+'one-minus-constant'];
+
+
+/** List of all GPUBlendOperation values. */
+export const kBlendOperations = [
+'add', //
+'subtract',
+'reverse-subtract',
+'min',
+'max'];
+
+
+// Primitive topologies
+export const kPrimitiveTopology = [
+'point-list',
+'line-list',
+'line-strip',
+'triangle-list',
+'triangle-strip'];
+
+assertTypeTrue();
+
+export const kIndexFormat = ['uint16', 'uint32'];
+assertTypeTrue();
+
+/** Info for each entry of GPUSupportedLimits */
+const [kLimitInfoKeys, kLimitInfoDefaults, kLimitInfoData] =
+[
+['class', 'core', 'compatibility', 'maximumValue'],
+['maximum',,, kMaxUnsignedLongValue], {
+ 'maxTextureDimension1D': [, 8192, 4096],
+ 'maxTextureDimension2D': [, 8192, 4096],
+ 'maxTextureDimension3D': [, 2048, 1024],
+ 'maxTextureArrayLayers': [, 256, 256],
+
+ 'maxBindGroups': [, 4, 4],
+ 'maxBindGroupsPlusVertexBuffers': [, 24, 24],
+ 'maxBindingsPerBindGroup': [, 1000, 1000],
+ 'maxDynamicUniformBuffersPerPipelineLayout': [, 8, 8],
+ 'maxDynamicStorageBuffersPerPipelineLayout': [, 4, 4],
+ 'maxSampledTexturesPerShaderStage': [, 16, 16],
+ 'maxSamplersPerShaderStage': [, 16, 16],
+ 'maxStorageBuffersPerShaderStage': [, 8, 4],
+ 'maxStorageTexturesPerShaderStage': [, 4, 4],
+ 'maxUniformBuffersPerShaderStage': [, 12, 12],
+
+ 'maxUniformBufferBindingSize': [, 65536, 16384, kMaxUnsignedLongLongValue],
+ 'maxStorageBufferBindingSize': [, 134217728, 134217728, kMaxUnsignedLongLongValue],
+ 'minUniformBufferOffsetAlignment': ['alignment', 256, 256],
+ 'minStorageBufferOffsetAlignment': ['alignment', 256, 256],
+
+ 'maxVertexBuffers': [, 8, 8],
+ 'maxBufferSize': [, 268435456, 268435456, kMaxUnsignedLongLongValue],
+ 'maxVertexAttributes': [, 16, 16],
+ 'maxVertexBufferArrayStride': [, 2048, 2048],
+ 'maxInterStageShaderComponents': [, 60, 60],
+ 'maxInterStageShaderVariables': [, 16, 16],
+
+ 'maxColorAttachments': [, 8, 4],
+ 'maxColorAttachmentBytesPerSample': [, 32, 32],
+
+ 'maxComputeWorkgroupStorageSize': [, 16384, 16384],
+ 'maxComputeInvocationsPerWorkgroup': [, 256, 128],
+ 'maxComputeWorkgroupSizeX': [, 256, 128],
+ 'maxComputeWorkgroupSizeY': [, 256, 128],
+ 'maxComputeWorkgroupSizeZ': [, 64, 64],
+ 'maxComputeWorkgroupsPerDimension': [, 65535, 65535]
+}];
+
+/**
+ * Feature levels corresponding to core WebGPU and WebGPU
+ * in compatibility mode. They can be passed to
+ * getDefaultLimits though if you have access to an adapter
+ * it's preferred to use getDefaultLimitsForAdapter.
+ */
+export const kFeatureLevels = ['core', 'compatibility'];
+
+
+const kLimitKeys = ['class', 'default', 'maximumValue'];
+
+const kLimitInfoCore = makeTableRenameAndFilter(
+ { default: 'core' },
+ kLimitKeys,
+ kLimitInfoKeys,
+ kLimitInfoDefaults,
+ kLimitInfoData
+);
+
+const kLimitInfoCompatibility = makeTableRenameAndFilter(
+ { default: 'compatibility' },
+ kLimitKeys,
+ kLimitInfoKeys,
+ kLimitInfoDefaults,
+ kLimitInfoData
+);
+
+const kLimitInfos = {
+ core: kLimitInfoCore,
+ compatibility: kLimitInfoCompatibility
+};
+
+export const kLimitClasses = Object.fromEntries(
+ Object.entries(kLimitInfoCore).map(([k, { class: c }]) => [k, c])
+);
+
+export function getDefaultLimits(featureLevel) {
+ return kLimitInfos[featureLevel];
+}
+
+export function getDefaultLimitsForAdapter(adapter) {
+ // MAINTENANCE_TODO: Remove casts when GPUAdapter IDL has isCompatibilityMode.
+ return getDefaultLimits(
+ adapter.isCompatibilityMode ?
+ 'compatibility' :
+ 'core'
+ );
+}
+
+/** List of all entries of GPUSupportedLimits. */
+export const kLimits = keysOf(kLimitInfoCore);
+
+/**
+ * The number of color attachments to test.
+ * The CTS needs to generate a consistent list of tests.
+ * We can't use any default limits since they different from core to compat mode
+ * So, tests should use this value and filter out any values that are out of
+ * range for the current device.
+ *
+ * The test in maxColorAttachments.spec.ts tests that kMaxColorAttachmentsToTest
+ * is large enough to cover all devices tested.
+ */
+export const kMaxColorAttachmentsToTest = 32;
+
+/** The size of indirect draw parameters in the indirectBuffer of drawIndirect */
+export const kDrawIndirectParametersSize = 4;
+/** The size of indirect drawIndexed parameters in the indirectBuffer of drawIndexedIndirect */
+export const kDrawIndexedIndirectParametersSize = 5;
+
+/** Per-GPUFeatureName info. */
+export const kFeatureNameInfo =
+
+
+{
+ 'bgra8unorm-storage': {},
+ 'depth-clip-control': {},
+ 'depth32float-stencil8': {},
+ 'texture-compression-bc': {},
+ 'texture-compression-etc2': {},
+ 'texture-compression-astc': {},
+ 'timestamp-query': {},
+ 'indirect-first-instance': {},
+ 'shader-f16': {},
+ 'rg11b10ufloat-renderable': {},
+ 'float32-filterable': {}
+};
+/** List of all GPUFeatureName values. */
+export const kFeatureNames = keysOf(kFeatureNameInfo); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.js
new file mode 100644
index 0000000000..30db0b1a40
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.js
@@ -0,0 +1,44 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests limitations of copyTextureToBuffer in compat mode.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { kCompressedTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import { align } from '../../../../../util/math.js';
+import { CompatibilityTest } from '../../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('compressed').
+desc(`Tests that you can not call copyTextureToBuffer 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, bytesPerBlock } = kTextureFormatInfo[format];
+
+ const texture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ usage: GPUTextureUsage.COPY_SRC
+ });
+ t.trackForCleanup(texture);
+
+ const bytesPerRow = align(bytesPerBlock, 256);
+
+ const buffer = t.device.createBuffer({
+ size: bytesPerRow,
+ usage: GPUBufferUsage.COPY_DST
+ });
+ t.trackForCleanup(buffer);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, [blockWidth, blockHeight, 1]);
+ t.expectGPUError('validation', () => {
+ encoder.finish();
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js
new file mode 100644
index 0000000000..8a3d64fe54
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.js
@@ -0,0 +1,423 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests limitations of bind group usage in a pipeline in compat mode.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { kRenderEncodeTypes } from '../../../../../util/command_buffer_maker.js';
+import { CompatibilityTest } from '../../../../compatibility_test.js';
+
+const kTextureTypes = ['regular', 'storage'];
+
+
+function getTextureTypeWGSL(textureType) {
+ return textureType === 'storage' ? 'texture_storage_2d<rgba8unorm, write>' : 'texture_2d<f32>';
+}
+
+
+
+
+/**
+ * Gets the WGSL needed for testing a render pipeline using texture_2d or texture_storage_2d
+ * and either 2 bindgroups or 1
+ */
+function getRenderShaderModule(
+device,
+textureType,
+bindConfig)
+{
+ const textureTypeWGSL = getTextureTypeWGSL(textureType);
+ const secondGroup = bindConfig === 'one bindgroup' ? 0 : 1;
+ const secondBinding = secondGroup === 0 ? 1 : 0;
+ return device.createShaderModule({
+ code: `
+ @vertex
+ fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
+ var pos = array(
+ vec4f(-1, 3, 0, 1),
+ vec4f( 3, -1, 0, 1),
+ vec4f(-1, -1, 0, 1));
+ return pos[VertexIndex];
+ }
+
+ @group(0) @binding(0) var tex0 : ${textureTypeWGSL};
+ @group(${secondGroup}) @binding(${secondBinding}) var tex1 : ${textureTypeWGSL};
+
+ @fragment
+ fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
+ _ = tex0;
+ _ = tex1;
+ return vec4f(0);
+ }
+ `
+ });
+}
+
+/**
+ * Gets the WGSL needed for testing a compute pipeline using texture_2d or texture_storage_2d
+ * and either 2 bindgroups or 1
+ */
+function getComputeShaderModule(
+device,
+textureType,
+bindConfig)
+{
+ const textureTypeWGSL = getTextureTypeWGSL(textureType);
+ const secondGroup = bindConfig === 'one bindgroup' ? 0 : 1;
+ const secondBinding = secondGroup === 0 ? 1 : 0;
+ return device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var tex0 : ${textureTypeWGSL};
+ @group(${secondGroup}) @binding(${secondBinding}) var tex1 : ${textureTypeWGSL};
+
+ @compute @workgroup_size(1)
+ fn cs() {
+ _ = tex0;
+ _ = tex1;
+ }
+ `
+ });
+}
+
+
+
+const kBindCases =
+
+
+
+
+
+
+
+
+
+
+
+{
+ 'incompatible views in the same bindGroup': {
+ bindConfig: 'one bindgroup',
+ fn(device, pipeline, encoder, texture) {
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 0, mipLevelCount: 1 }) },
+ { binding: 1, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+
+ });
+ encoder.setBindGroup(0, bindGroup);
+ return { shouldSucceed: false };
+ }
+ },
+ 'incompatible views in different bindGroups': {
+ bindConfig: 'two bindgroups',
+ fn(device, pipeline, encoder, texture) {
+ const bindGroup0 = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 0, mipLevelCount: 1 }) }]
+
+ });
+ const bindGroup1 = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(1),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+
+ });
+ encoder.setBindGroup(0, bindGroup0);
+ encoder.setBindGroup(1, bindGroup1);
+ return { shouldSucceed: false };
+ }
+ },
+ 'can bind same view in different bindGroups': {
+ bindConfig: 'two bindgroups',
+ fn(device, pipeline, encoder, texture) {
+ const bindGroup0 = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+
+ });
+ const bindGroup1 = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(1),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+
+ });
+ encoder.setBindGroup(0, bindGroup0);
+ encoder.setBindGroup(1, bindGroup1);
+ return { shouldSucceed: true };
+ }
+ },
+ 'binding incompatible bindGroups then fix': {
+ bindConfig: 'one bindgroup',
+ fn(device, pipeline, encoder, texture) {
+ const badBindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 0, mipLevelCount: 1 }) },
+ { binding: 1, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+
+ });
+ const goodBindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) },
+ { binding: 1, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+
+ });
+ encoder.setBindGroup(0, badBindGroup);
+ encoder.setBindGroup(0, goodBindGroup);
+ return { shouldSucceed: true };
+ }
+ }
+};
+
+function createAndBindTwoBindGroupsWithDifferentViewsOfSameTexture(
+device,
+pipeline,
+encoder,
+texture)
+{
+ const bindGroup0 = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: texture.createView({ baseMipLevel: 0, mipLevelCount: 1 }) }]
+ });
+ const bindGroup1 = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(1),
+ entries: [{ binding: 0, resource: texture.createView({ baseMipLevel: 1, mipLevelCount: 1 }) }]
+ });
+ encoder.setBindGroup(0, bindGroup0);
+ encoder.setBindGroup(1, bindGroup1);
+}
+
+const kBindCaseNames = keysOf(kBindCases);
+
+const kDrawUseCases =
+
+{
+ draw: (_t, encoder) => {
+ encoder.draw(3);
+ },
+ drawIndexed: (t, encoder) => {
+ const indexBuffer = t.makeBufferWithContents(new Uint16Array([0, 1, 2]), GPUBufferUsage.INDEX);
+ encoder.setIndexBuffer(indexBuffer, 'uint16');
+ encoder.drawIndexed(3);
+ },
+ drawIndirect(t, encoder) {
+ const indirectBuffer = t.makeBufferWithContents(
+ new Uint32Array([3, 1, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ );
+ encoder.drawIndirect(indirectBuffer, 0);
+ },
+ drawIndexedIndirect(t, encoder) {
+ const indexBuffer = t.makeBufferWithContents(new Uint16Array([0, 1, 2]), GPUBufferUsage.INDEX);
+ encoder.setIndexBuffer(indexBuffer, 'uint16');
+ const indirectBuffer = t.makeBufferWithContents(
+ new Uint32Array([3, 1, 0, 0, 0]),
+ GPUBufferUsage.INDIRECT
+ );
+ encoder.drawIndexedIndirect(indirectBuffer, 0);
+ }
+};
+const kDrawCaseNames = keysOf(kDrawUseCases);
+
+const kDispatchUseCases =
+
+{
+ dispatchWorkgroups(_t, encoder) {
+ encoder.dispatchWorkgroups(1);
+ },
+ dispatchWorkgroupsIndirect(t, encoder) {
+ const indirectBuffer = t.makeBufferWithContents(
+ new Uint32Array([1, 1, 1]),
+ GPUBufferUsage.INDIRECT
+ );
+ encoder.dispatchWorkgroupsIndirect(indirectBuffer, 0);
+ }
+};
+const kDispatchCaseNames = keysOf(kDispatchUseCases);
+
+function createResourcesForRenderPassTest(
+t,
+textureType,
+bindConfig)
+{
+ const texture = t.device.createTexture({
+ size: [2, 1, 1],
+ mipLevelCount: 2,
+ format: 'rgba8unorm',
+ usage:
+ textureType === 'storage' ? GPUTextureUsage.STORAGE_BINDING : GPUTextureUsage.TEXTURE_BINDING
+ });
+ t.trackForCleanup(texture);
+
+ const module = getRenderShaderModule(t.device, textureType, bindConfig);
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+
+ return { texture, pipeline };
+}
+
+function createResourcesForComputePassTest(
+t,
+textureType,
+bindConfig)
+{
+ const texture = t.device.createTexture({
+ size: [2, 1, 1],
+ mipLevelCount: 2,
+ format: 'rgba8unorm',
+ usage:
+ textureType === 'storage' ? GPUTextureUsage.STORAGE_BINDING : GPUTextureUsage.TEXTURE_BINDING
+ });
+ t.trackForCleanup(texture);
+
+ const module = getComputeShaderModule(t.device, textureType, bindConfig);
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'cs'
+ }
+ });
+
+ return { texture, pipeline };
+}
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('twoDifferentTextureViews,render_pass,used').
+desc(
+ `
+Tests that you can not use 2 different views of the same texture in a render pass in compat mode.
+
+- Test you can not use incompatible views in the same bindGroup
+- Test you can not use incompatible views in different bindGroups
+- Test you can bind the same view in different bindGroups
+- Test binding incompatible bindGroups is ok as long as they are fixed before draw/dispatch
+
+ The last test is to check validation happens at the correct time (draw/dispatch) and not
+ at setBindGroup.
+ `
+).
+params((u) =>
+u.
+combine('encoderType', kRenderEncodeTypes).
+combine('bindCase', kBindCaseNames).
+combine('useCase', kDrawCaseNames).
+combine('textureType', kTextureTypes).
+filter(
+ // storage textures can't have 2 bind groups point to the same
+ // view even in non-compat. They can have different views in
+ // non-compat but not compat.
+ (p) =>
+ !(
+ p.textureType === 'storage' && (
+ p.bindCase === 'can bind same view in different bindGroups' ||
+ p.bindCase === 'binding incompatible bindGroups then fix'))
+
+)
+).
+fn((t) => {
+ const { encoderType, bindCase, useCase, textureType } = t.params;
+ const { bindConfig, fn } = kBindCases[bindCase];
+ const { texture, pipeline } = createResourcesForRenderPassTest(t, textureType, bindConfig);
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ const { shouldSucceed } = fn(t.device, pipeline, encoder, texture);
+ kDrawUseCases[useCase](t, encoder);
+ validateFinish(shouldSucceed);
+});
+
+g.test('twoDifferentTextureViews,render_pass,unused').
+desc(
+ `
+Tests that binding 2 different views of the same texture but not using them does not generate a validation error.
+ `
+).
+params((u) => u.combine('encoderType', kRenderEncodeTypes).combine('textureType', kTextureTypes)).
+fn((t) => {
+ const { encoderType, textureType } = t.params;
+ const { texture, pipeline } = createResourcesForRenderPassTest(
+ t,
+ textureType,
+ 'two bindgroups'
+ );
+ const { encoder, validateFinish } = t.createEncoder(encoderType);
+ encoder.setPipeline(pipeline);
+ createAndBindTwoBindGroupsWithDifferentViewsOfSameTexture(t.device, pipeline, encoder, texture);
+ validateFinish(true);
+});
+
+g.test('twoDifferentTextureViews,compute_pass,used').
+desc(
+ `
+Tests that you can not use 2 different views of the same texture in a compute pass in compat mode.
+
+- Test you can not use incompatible views in the same bindGroup
+- Test you can not use incompatible views in different bindGroups
+- Test can bind the same view in different bindGroups
+- Test that binding incompatible bindGroups is ok as long as they are fixed before draw/dispatch
+
+ The last test is to check validation happens at the correct time (draw/dispatch) and not
+ at setBindGroup.
+ `
+).
+params((u) =>
+u.
+combine('bindCase', kBindCaseNames).
+combine('useCase', kDispatchCaseNames).
+combine('textureType', kTextureTypes).
+filter(
+ // storage textures can't have 2 bind groups point to the same
+ // view even in non-compat. They can have different views in
+ // non-compat but not compat.
+ (p) =>
+ !(
+ p.textureType === 'storage' && (
+ p.bindCase === 'can bind same view in different bindGroups' ||
+ p.bindCase === 'binding incompatible bindGroups then fix'))
+
+)
+).
+fn((t) => {
+ const { bindCase, useCase, textureType } = t.params;
+ const { bindConfig, fn } = kBindCases[bindCase];
+ const { texture, pipeline } = createResourcesForComputePassTest(t, textureType, bindConfig);
+ const { encoder, validateFinish } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ const { shouldSucceed } = fn(t.device, pipeline, encoder, texture);
+ kDispatchUseCases[useCase](t, encoder);
+ validateFinish(shouldSucceed);
+});
+
+g.test('twoDifferentTextureViews,compute_pass,unused').
+desc(
+ `
+Tests that binding 2 different views of the same texture but not using them does not generate a validation error.
+ `
+).
+params((u) => u.combine('textureType', kTextureTypes)).
+fn((t) => {
+ const { textureType } = t.params;
+ const { texture, pipeline } = createResourcesForComputePassTest(
+ t,
+ textureType,
+ 'two bindgroups'
+ );
+ const { encoder, validateFinish } = t.createEncoder('compute pass');
+ encoder.setPipeline(pipeline);
+ createAndBindTwoBindGroupsWithDifferentViewsOfSameTexture(t.device, pipeline, encoder, texture);
+ validateFinish(true);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/fragment_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/fragment_state.spec.js
new file mode 100644
index 0000000000..b3c41daafa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/fragment_state.spec.js
@@ -0,0 +1,128 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests that you can not create a render pipeline with different per target blend state or write mask in compat mode.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../common/util/data_tables.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+
+
+const cases = {
+ default(_targets) {
+ return true;
+ },
+ noBlendTarget0(targets) {
+ delete targets[0].blend;
+ return false;
+ },
+ noBlendTarget1(targets) {
+ delete targets[2].blend;
+ return false;
+ },
+ colorOperation(targets) {
+ targets[2].blend.color.operation = 'subtract';
+ return false;
+ },
+ colorSrcFactor(targets) {
+ targets[2].blend.color.srcFactor = 'src-alpha';
+ return false;
+ },
+ colorDstFactor(targets) {
+ targets[2].blend.color.dstFactor = 'dst-alpha';
+ return false;
+ },
+ alphaOperation(targets) {
+ targets[2].blend.alpha.operation = 'subtract';
+ return false;
+ },
+ alphaSrcFactor(targets) {
+ targets[2].blend.alpha.srcFactor = 'src-alpha';
+ return false;
+ },
+ alphaDstFactor(targets) {
+ targets[2].blend.alpha.dstFactor = 'dst-alpha';
+ return false;
+ },
+ writeMask(targets) {
+ targets[2].writeMask = GPUColorWrite.GREEN;
+ return false;
+ }
+};
+const caseNames = keysOf(cases);
+
+g.test('colorState').
+desc(
+ `
+Tests that you can not create a render pipeline with different per target blend state or write mask in compat mode.
+
+- Test no blend state vs some blend state
+- Test different operation, srcFactor, dstFactor for color and alpha
+- Test different writeMask
+ `
+).
+params((u) => u.combine('caseName', caseNames)).
+fn((t) => {
+ const { caseName } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+
+ struct FragmentOut {
+ @location(0) fragColor0 : vec4f,
+ @location(1) fragColor1 : vec4f,
+ @location(2) fragColor2 : vec4f,
+ }
+
+ @fragment fn fs() -> FragmentOut {
+ var output : FragmentOut;
+ output.fragColor0 = vec4f(0);
+ output.fragColor1 = vec4f(0);
+ output.fragColor2 = vec4f(0);
+ return output;
+ }
+ `
+ });
+
+ const targets = [
+ {
+ format: 'rgba8unorm',
+ blend: {
+ color: {},
+ alpha: {}
+ }
+ },
+ null,
+ {
+ format: 'rgba8unorm',
+ blend: {
+ color: {},
+ alpha: {}
+ }
+ }];
+
+
+ const pipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets
+ }
+ };
+ const isValid = cases[caseName](targets);
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createRenderPipeline(pipelineDescriptor),
+ !isValid
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/shader_module.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/shader_module.spec.js
new file mode 100644
index 0000000000..5218456a84
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/shader_module.spec.js
@@ -0,0 +1,74 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests limitations of createRenderPipeline related to shader modules in compat mode.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('sample_mask').
+desc(
+ `
+Tests that you can not create a render pipeline with a shader module that uses sample_mask in compat mode.
+
+- Test that a pipeline with a shader that uses sample_mask fails.
+- Test that a pipeline that references a module that has a shader that uses sample_mask
+ but the pipeline does not reference that shader succeeds.
+ `
+).
+params((u) =>
+u.combine('entryPoint', ['fsWithoutSampleMaskUsage', 'fsWithSampleMaskUsage'])
+).
+fn((t) => {
+ const { entryPoint } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(1);
+ }
+ struct Output {
+ @builtin(sample_mask) mask_out: u32,
+ @location(0) color : vec4f,
+ }
+ @fragment fn fsWithoutSampleMaskUsage() -> @location(0) vec4f {
+ return vec4f(1.0, 1.0, 1.0, 1.0);
+ }
+ @fragment fn fsWithSampleMaskUsage() -> Output {
+ var o: Output;
+ // We need to make sure this sample_mask isn't optimized out even if its value equals "no op".
+ o.mask_out = 0xFFFFFFFFu;
+ o.color = vec4f(1.0, 1.0, 1.0, 1.0);
+ return o;
+ }
+ `
+ });
+
+ const pipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint,
+ targets: [
+ {
+ format: 'rgba8unorm'
+ }]
+
+ },
+ multisample: {
+ count: 4
+ }
+ };
+
+ const isValid = entryPoint === 'fsWithoutSampleMaskUsage';
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createRenderPipeline(pipelineDescriptor),
+ !isValid
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.js
new file mode 100644
index 0000000000..d5ba060e83
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/render_pipeline/vertex_state.spec.js
@@ -0,0 +1,91 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests limitations of createRenderPipeline related to vertex state in compat mode.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { range } from '../../../../../common/util/util.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('maxVertexAttributesVertexIndexInstanceIndex').
+desc(
+ `
+Tests @builtin(vertex_index) and @builtin(instance_index) each count as an attribute.
+
+- Test that you can use maxVertexAttributes
+- Test that you can not use maxVertexAttributes and @builtin(vertex_index)
+- Test that you can not use maxVertexAttributes and @builtin(instance_index)
+- Test that you can use maxVertexAttributes - 1 and @builtin(vertex_index)
+- Test that you can use maxVertexAttributes - 1 and @builtin(instance_index)
+- Test that you can not use maxVertexAttributes - 1 and both @builtin(vertex_index) and @builtin(instance_index)
+- Test that you can use maxVertexAttributes - 2 and both @builtin(vertex_index) and @builtin(instance_index)
+ `
+).
+params((u) =>
+u.
+combine('useVertexIndex', [false, true]).
+combine('useInstanceIndex', [false, true]).
+combine('numAttribsToReserve', [0, 1, 2]).
+combine('isAsync', [false, true])
+).
+fn((t) => {
+ const { useVertexIndex, useInstanceIndex, numAttribsToReserve, isAsync } = t.params;
+ const numAttribs = t.device.limits.maxVertexAttributes - numAttribsToReserve;
+
+ const numBuiltinsUsed = (useVertexIndex ? 1 : 0) + (useInstanceIndex ? 1 : 0);
+ const isValid = numAttribs + numBuiltinsUsed <= t.device.limits.maxVertexAttributes;
+
+ const inputs = range(numAttribs, (i) => `@location(${i}) v${i}: vec4f`);
+ const outputs = range(numAttribs, (i) => `v${i}`);
+
+ if (useVertexIndex) {
+ inputs.push('@builtin(vertex_index) vNdx: u32');
+ outputs.push('vec4f(f32(vNdx))');
+ }
+
+ if (useInstanceIndex) {
+ inputs.push('@builtin(instance_index) iNdx: u32');
+ outputs.push('vec4f(f32(iNdx))');
+ }
+
+ const module = t.device.createShaderModule({
+ code: `
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ @vertex fn vs(${inputs.join(', ')}) -> @builtin(position) vec4f {
+ return ${outputs.join(' + ')};
+ }
+ `
+ });
+
+ const pipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ buffers: [
+ {
+ arrayStride: 16,
+ attributes: range(numAttribs, (i) => ({
+ shaderLocation: i,
+ format: 'float32x4',
+ offset: 0
+ }))
+ }]
+
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [
+ {
+ format: 'rgba8unorm'
+ }]
+
+ }
+ };
+
+ t.doCreateRenderPipelineTest(isAsync, isValid, pipelineDescriptor);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/createTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/createTexture.spec.js
new file mode 100644
index 0000000000..c767301b17
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/createTexture.spec.js
@@ -0,0 +1,41 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests that you can not use bgra8unorm-srgb in compat mode.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('unsupportedTextureFormats').
+desc(`Tests that you can not create a bgra8unorm-srgb texture in compat mode.`).
+fn((t) => {
+ t.expectGPUError(
+ 'validation',
+ () =>
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'bgra8unorm-srgb',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ }),
+ true
+ );
+});
+
+g.test('unsupportedTextureViewFormats').
+desc(
+ `Tests that you can not create a bgra8unorm texture with a bgra8unorm-srgb viewFormat in compat mode.`
+).
+fn((t) => {
+ t.expectGPUError(
+ 'validation',
+ () =>
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'bgra8unorm',
+ viewFormats: ['bgra8unorm-srgb'],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ }),
+ true
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/cubeArray.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/cubeArray.spec.js
new file mode 100644
index 0000000000..ed3c96ec34
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/api/validation/texture/cubeArray.spec.js
@@ -0,0 +1,26 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests that you can not create cube array views in compat mode.
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+g.test('cube_array').
+desc('Test you cannot create a cube array texture view.').
+params((u) => u.combine('dimension', ['cube', 'cube-array'])).
+fn((t) => {
+ const { dimension } = t.params;
+ const texture = t.device.createTexture({
+ size: [1, 1, 6],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ const isValid = dimension === 'cube';
+ t.expectGPUError(
+ 'validation',
+ () => texture.createView({ dimension, format: 'rgba8unorm' }),
+ !isValid
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/compatibility_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/compatibility_test.js
new file mode 100644
index 0000000000..217dbc0ac1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/compat/compatibility_test.js
@@ -0,0 +1,10 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { ValidationTest } from '../api/validation/validation_test.js';export class CompatibilityTest extends ValidationTest {
+ async init() {
+ await super.init();
+ if (!this.isCompatibility) {
+ this.skip('compatibility tests do not work on non-compatibility mode');
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/constants.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/constants.js
new file mode 100644
index 0000000000..b8dbfcd9a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/constants.js
@@ -0,0 +1,62 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // Note: Types ensure every field is specified.
+function checkType(_) {}
+
+const BufferUsage = {
+ MAP_READ: 0x0001,
+ MAP_WRITE: 0x0002,
+ COPY_SRC: 0x0004,
+ COPY_DST: 0x0008,
+ INDEX: 0x0010,
+ VERTEX: 0x0020,
+ UNIFORM: 0x0040,
+ STORAGE: 0x0080,
+ INDIRECT: 0x0100,
+ QUERY_RESOLVE: 0x0200
+};
+checkType(BufferUsage);
+
+const TextureUsage = {
+ COPY_SRC: 0x01,
+ COPY_DST: 0x02,
+ TEXTURE_BINDING: 0x04,
+ SAMPLED: 0x04,
+ STORAGE_BINDING: 0x08,
+ STORAGE: 0x08,
+ RENDER_ATTACHMENT: 0x10
+};
+checkType(TextureUsage);
+
+const ColorWrite = {
+ RED: 0x1,
+ GREEN: 0x2,
+ BLUE: 0x4,
+ ALPHA: 0x8,
+ ALL: 0xf
+};
+checkType(ColorWrite);
+
+const ShaderStage = {
+ VERTEX: 0x1,
+ FRAGMENT: 0x2,
+ COMPUTE: 0x4
+};
+checkType(ShaderStage);
+
+const MapMode = {
+ READ: 0x1,
+ WRITE: 0x2
+};
+checkType(MapMode);
+
+export const GPUConst = {
+ BufferUsage,
+ TextureUsage,
+ ColorWrite,
+ ShaderStage,
+ MapMode
+};
+
+export const kMaxUnsignedLongValue = 4294967295;
+export const kMaxUnsignedLongLongValue = Number.MAX_SAFE_INTEGER; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/examples.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/examples.spec.js
new file mode 100644
index 0000000000..ca72493de6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/examples.spec.js
@@ -0,0 +1,275 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Examples of writing CTS tests with various features.
+
+Start here when looking for examples of basic framework usage.
+`;import { makeTestGroup } from '../common/framework/test_group.js';
+
+import { GPUTest } from './gpu_test.js';
+
+// To run these tests in the standalone runner, run `npm start` then open:
+// - http://localhost:XXXX/standalone/?runnow=1&q=webgpu:examples:*
+// To run in WPT, copy/symlink the out-wpt/ directory as the webgpu/ directory in WPT, then open:
+// - (wpt server url)/webgpu/cts.https.html?q=webgpu:examples:
+//
+// Tests here can be run individually or in groups:
+// - ?q=webgpu:examples:basic,async:
+// - ?q=webgpu:examples:basic,async:*
+// - ?q=webgpu:examples:basic,*
+// - ?q=webgpu:examples:*
+
+export const g = makeTestGroup(GPUTest);
+
+// Note: spaces aren't allowed in test names; use underscores.
+g.test('test_name').fn((_t) => {});
+
+g.test('not_implemented_yet,without_plan').unimplemented();
+g.test('not_implemented_yet,with_plan').
+desc(
+ `
+Plan for this test. What it tests. Summary of how it tests that functionality.
+- Description of cases, by describing parameters {a, b, c}
+- x= more parameters {x, y, z}
+`
+).
+unimplemented();
+
+g.test('basic').fn((t) => {
+ t.expect(true);
+ t.expect(true, 'true should be true');
+
+ t.shouldThrow(
+ // The expected '.name' of the thrown error.
+ 'TypeError',
+ // This function is run inline inside shouldThrow, and is expected to throw.
+ () => {
+ throw new TypeError();
+ },
+ // Log message.
+ { message: 'function should throw Error' }
+ );
+});
+
+g.test('basic,async').fn((t) => {
+ // shouldReject must be awaited to ensure it can wait for the promise before the test ends.
+ t.shouldReject(
+ // The expected '.name' of the thrown error.
+ 'TypeError',
+ // Promise expected to reject.
+ Promise.reject(new TypeError()),
+ // Log message.
+ { message: 'Promise.reject should reject' }
+ );
+
+ // Promise can also be an IIFE (immediately-invoked function expression).
+ t.shouldReject(
+ 'TypeError',
+
+ (async () => {
+ throw new TypeError();
+ })(),
+ { message: 'Promise.reject should reject' }
+ );
+});
+
+g.test('basic,plain_cases').
+desc(
+ `
+A test can be parameterized with a simple array of objects using .paramsSimple([ ... ]).
+Each such instance of the test is a "case".
+
+In this example, the following cases are generated (identified by their "query string"),
+each with just one subcase:
+ - webgpu:examples:basic,cases:x=2;y=2 runs 1 subcase, with t.params set to:
+ - { x: 2, y: 2 }
+ - webgpu:examples:basic,cases:x=-10;y=-10 runs 1 subcase, with t.params set to:
+ - { x: -10, y: -10 }
+ `
+).
+paramsSimple([
+{ x: 2, y: 2 }, //
+{ x: -10, y: -10 }]
+).
+fn((t) => {
+ t.expect(t.params.x === t.params.y);
+});
+
+g.test('basic,plain_cases_private').
+desc(
+ `
+Parameters can be public ("x", "y") which means they're part of the case name.
+They can also be private by starting with an underscore ("_result"), which passes
+them into the test but does not make them part of the case name:
+
+In this example, the following cases are generated, each with just one subcase:
+ - webgpu:examples:basic,cases:x=2;y=4 runs 1 subcase, with t.params set to:
+ - { x: 2, y: 4, _result: 6 }
+ - webgpu:examples:basic,cases:x=-10;y=18 runs 1 subcase, with t.params set to:
+ - { x: -10, y: 18, _result: 8 }
+ `
+).
+paramsSimple([
+{ x: 2, y: 4, _result: 6 }, //
+{ x: -10, y: 18, _result: 8 }]
+).
+fn((t) => {
+ t.expect(t.params.x + t.params.y === t.params._result);
+});
+// (note the blank comment above to enforce newlines on autoformat)
+
+g.test('basic,builder_cases').
+desc(
+ `
+A "CaseParamsBuilder" or "SubcaseParamsBuilder" can be passed to .params() instead.
+The params builder provides facilities for generating tests combinatorially (by cartesian
+product). For convenience, the "unit" CaseParamsBuilder is passed as an argument ("u" below).
+
+In this example, the following cases are generated, each with just one subcase:
+ - webgpu:examples:basic,cases:x=1,y=1 runs 1 subcase, with t.params set to:
+ - { x: 1, y: 1 }
+ - webgpu:examples:basic,cases:x=1,y=2 runs 1 subcase, with t.params set to:
+ - { x: 1, y: 2 }
+ - webgpu:examples:basic,cases:x=2,y=1 runs 1 subcase, with t.params set to:
+ - { x: 2, y: 1 }
+ - webgpu:examples:basic,cases:x=2,y=2 runs 1 subcase, with t.params set to:
+ - { x: 2, y: 2 }
+ `
+).
+params((u) =>
+u //
+.combineWithParams([{ x: 1 }, { x: 2 }]).
+combineWithParams([{ y: 1 }, { y: 2 }])
+).
+fn(() => {});
+
+g.test('basic,builder_cases_subcases').
+desc(
+ `
+Each case sub-parameterized using .beginSubcases().
+Each such instance of the test is a "subcase", which cannot be run independently of other
+subcases. It is somewhat like wrapping the entire fn body in a for-loop.
+
+In this example, the following cases are generated:
+ - webgpu:examples:basic,cases:x=1 runs 2 subcases, with t.params set to:
+ - { x: 1, y: 1 }
+ - { x: 1, y: 2 }
+ - webgpu:examples:basic,cases:x=2 runs 2 subcases, with t.params set to:
+ - { x: 2, y: 1 }
+ - { x: 2, y: 2 }
+ `
+).
+params((u) =>
+u //
+.combineWithParams([{ x: 1 }, { x: 2 }]).
+beginSubcases().
+combineWithParams([{ y: 1 }, { y: 2 }])
+).
+fn(() => {});
+
+g.test('basic,builder_subcases').
+desc(
+ `
+In this example, the following single case is generated:
+ - webgpu:examples:basic,cases: runs 4 subcases, with t.params set to:
+ - { x: 1, y: 1 }
+ - { x: 1, y: 2 }
+ - { x: 2, y: 1 }
+ - { x: 2, y: 2 }
+ `
+).
+params((u) =>
+u //
+.beginSubcases().
+combineWithParams([{ x: 1 }, { x: 2 }]).
+combineWithParams([{ y: 1 }, { y: 2 }])
+).
+fn(() => {});
+
+g.test('basic,builder_subcases_short').
+desc(
+ `
+As a shorthand, .paramsSubcasesOnly() can be used.
+
+In this example, the following single case is generated:
+ - webgpu:examples:basic,cases: runs 4 subcases, with t.params set to:
+ - { x: 1, y: 1 }
+ - { x: 1, y: 2 }
+ - { x: 2, y: 1 }
+ - { x: 2, y: 2 }
+ `
+).
+paramsSubcasesOnly((u) =>
+u //
+.combineWithParams([{ x: 1 }, { x: 2 }]).
+combineWithParams([{ y: 1 }, { y: 2 }])
+).
+fn(() => {});
+
+g.test('gpu,async').fn(async (t) => {
+ const x = await t.queue.onSubmittedWorkDone();
+ t.expect(x === undefined);
+});
+
+g.test('gpu,buffers').fn((t) => {
+ const data = new Uint32Array([0, 1234, 0]);
+ const src = t.makeBufferWithContents(data, GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST);
+
+ // Use the expectGPUBufferValuesEqual helper to check the actual contents of a GPUBuffer.
+ // This makes a copy and then asynchronously checks the contents. The test fixture will
+ // wait on that result before reporting whether the test passed or failed.
+ t.expectGPUBufferValuesEqual(src, data);
+});
+
+// One of the following two tests should be skipped on most platforms.
+
+g.test('gpu,with_texture_compression,bc').
+desc(
+ `Example of a test using a device descriptor.
+Tests that a BC format passes validation iff the feature is enabled.`
+).
+params((u) => u.combine('textureCompressionBC', [false, true])).
+beforeAllSubcases((t) => {
+ const { textureCompressionBC } = t.params;
+
+ if (textureCompressionBC) {
+ t.selectDeviceOrSkipTestCase('texture-compression-bc');
+ }
+}).
+fn((t) => {
+ const { textureCompressionBC } = t.params;
+ const shouldError = !textureCompressionBC;
+ t.shouldThrow(shouldError ? 'TypeError' : false, () => {
+ t.device.createTexture({
+ format: 'bc1-rgba-unorm',
+ size: [4, 4, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+ });
+});
+
+g.test('gpu,with_texture_compression,etc2').
+desc(
+ `Example of a test using a device descriptor.
+Tests that an ETC2 format passes validation iff the feature is enabled.`
+).
+params((u) => u.combine('textureCompressionETC2', [false, true])).
+beforeAllSubcases((t) => {
+ const { textureCompressionETC2 } = t.params;
+
+ if (textureCompressionETC2) {
+ t.selectDeviceOrSkipTestCase('texture-compression-etc2');
+ }
+}).
+fn((t) => {
+ const { textureCompressionETC2 } = t.params;
+
+ const shouldError = !textureCompressionETC2;
+ t.shouldThrow(shouldError ? 'TypeError' : false, () => {
+ t.device.createTexture({
+ format: 'etc2-rgb8unorm',
+ size: [4, 4, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING
+ });
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/format_info.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/format_info.js
new file mode 100644
index 0000000000..5ba96b741b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/format_info.js
@@ -0,0 +1,1273 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { keysOf } from '../common/util/data_tables.js';import { assert } from '../common/util/util.js';
+import { align } from './util/math.js';
+
+
+//
+// Texture format tables
+//
+
+/**
+ * Defaults applied to all texture format tables automatically. Used only inside
+ * `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.
+ */
+const kFormatUniversalDefaults = {
+ blockWidth: undefined,
+ blockHeight: undefined,
+ color: undefined,
+ depth: undefined,
+ stencil: undefined,
+ colorRender: undefined,
+ multisample: undefined,
+ feature: undefined,
+ baseFormat: undefined,
+
+ sampleType: undefined,
+ copySrc: undefined,
+ copyDst: undefined,
+ bytesPerBlock: undefined,
+ renderable: false,
+ renderTargetPixelByteCost: undefined,
+ renderTargetComponentAlignment: undefined
+
+ // IMPORTANT:
+ // Add new top-level keys both here and in TextureFormatInfo_TypeCheck.
+};
+/**
+ * Takes `table` and applies `defaults` to every row, i.e. for each row,
+ * `{ ... kUniversalDefaults, ...defaults, ...row }`.
+ * This only operates at the first level; it doesn't support defaults in nested objects.
+ */
+function formatTableWithDefaults({
+ defaults,
+ table
+
+
+
+})
+
+
+
+
+
+
+
+{
+ return Object.fromEntries(
+ Object.entries(table).map(([k, row]) => [
+ k,
+ { ...kFormatUniversalDefaults, ...defaults, ...row }]
+ )
+
+ );
+}
+
+/** "plain color formats", plus rgb9e5ufloat. */
+const kRegularTextureFormatInfo = formatTableWithDefaults({
+ defaults: { blockWidth: 1, blockHeight: 1, copySrc: true, copyDst: true },
+ table: {
+ // plain, 8 bits per component
+
+ r8unorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ colorRender: { blend: true, resolve: true, byteCost: 1, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r8snorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r8uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ colorRender: { blend: false, resolve: false, byteCost: 1, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r8sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ colorRender: { blend: false, resolve: false, byteCost: 1, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ rg8unorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ colorRender: { blend: true, resolve: true, byteCost: 2, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg8snorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg8uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg8sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ rgba8unorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ baseFormat: 'rgba8unorm',
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'rgba8unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ baseFormat: 'rgba8unorm',
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba8snorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba8uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba8sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ bgra8unorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ baseFormat: 'bgra8unorm',
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bgra8unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ baseFormat: 'bgra8unorm',
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ // plain, 16 bits per component
+
+ r16uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r16sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r16float: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ colorRender: { blend: true, resolve: true, byteCost: 2, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ rg16uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg16sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg16float: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: true, resolve: true, byteCost: 4, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ rgba16uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba16sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba16float: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 2 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ // plain, 32 bits per component
+
+ r32uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r32sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ r32float: {
+ color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ rg32uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg32sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg32float: {
+ color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ rgba32uint: {
+ color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba32sint: {
+ color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgba32float: {
+ color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ 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 },
+ colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rgb10a2unorm: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 4 },
+ renderable: true,
+ get renderTargetComponentAlignment() {return this.colorRender.alignment;},
+ get renderTargetPixelByteCost() {return this.colorRender.byteCost;},
+ multisample: true,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ rg11b10ufloat: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;},
+ renderTargetPixelByteCost: 8,
+ renderTargetComponentAlignment: 4
+ },
+
+ // packed
+
+ rgb9e5ufloat: {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ multisample: false,
+ get sampleType() {return this.color.type;},
+ get bytesPerBlock() {return this.color.bytes;}
+ }
+ }
+});
+
+// MAINTENANCE_TODO: Distinguishing "sized" and "unsized" depth stencil formats doesn't make sense
+// 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 },
+ table: {
+ stencil8: {
+ stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ sampleType: 'uint',
+ copyDst: true,
+ bytesPerBlock: 1
+ },
+ depth16unorm: {
+ depth: { type: 'depth', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ sampleType: 'depth',
+ copyDst: true,
+ bytesPerBlock: 2
+ },
+ depth32float: {
+ depth: { type: 'depth', copySrc: true, copyDst: false, storage: false, bytes: 4 },
+ sampleType: 'depth',
+ copyDst: false,
+ bytesPerBlock: 4
+ }
+ }
+});
+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
+ },
+ '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
+ },
+ 'depth32float-stencil8': {
+ depth: { type: 'depth', copySrc: true, copyDst: false, storage: false, bytes: 4 },
+ stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ feature: 'depth32float-stencil8',
+ copySrc: false,
+ copyDst: false,
+ sampleType: 'depth',
+ renderable: true
+ }
+ }
+});
+
+const kBCTextureFormatInfo = formatTableWithDefaults({
+ defaults: {
+ blockWidth: 4,
+ 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 },
+ baseFormat: 'bc1-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc1-rgba-unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ baseFormat: 'bc1-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'bc2-rgba-unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'bc2-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc2-rgba-unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'bc2-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'bc3-rgba-unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'bc3-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc3-rgba-unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'bc3-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'bc4-r-unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc4-r-snorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'bc5-rg-unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc5-rg-snorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'bc6h-rgb-ufloat': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc6h-rgb-float': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'bc7-rgba-unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'bc7-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'bc7-rgba-unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'bc7-rgba-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ }
+ }
+});
+
+const kETC2TextureFormatInfo = formatTableWithDefaults({
+ defaults: {
+ blockWidth: 4,
+ 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 },
+ baseFormat: 'etc2-rgb8unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'etc2-rgb8unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ baseFormat: 'etc2-rgb8unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'etc2-rgb8a1unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ baseFormat: 'etc2-rgb8a1unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'etc2-rgb8a1unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ baseFormat: 'etc2-rgb8a1unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'etc2-rgba8unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'etc2-rgba8unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'etc2-rgba8unorm-srgb': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'etc2-rgba8unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'eac-r11unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'eac-r11snorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'eac-rg11unorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+ 'eac-rg11snorm': {
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ get bytesPerBlock() {return this.color.bytes;}
+ }
+ }
+});
+
+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 },
+ baseFormat: 'astc-4x4-unorm',
+ 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 },
+ baseFormat: 'astc-4x4-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-5x4-unorm': {
+ blockWidth: 5,
+ blockHeight: 4,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-5x4-unorm',
+ 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 },
+ baseFormat: 'astc-5x4-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-5x5-unorm': {
+ blockWidth: 5,
+ blockHeight: 5,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-5x5-unorm',
+ 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 },
+ baseFormat: 'astc-5x5-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-6x5-unorm': {
+ blockWidth: 6,
+ blockHeight: 5,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-6x5-unorm',
+ 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 },
+ baseFormat: 'astc-6x5-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-6x6-unorm': {
+ blockWidth: 6,
+ blockHeight: 6,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-6x6-unorm',
+ 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 },
+ baseFormat: 'astc-6x6-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-8x5-unorm': {
+ blockWidth: 8,
+ blockHeight: 5,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-8x5-unorm',
+ 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 },
+ baseFormat: 'astc-8x5-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-8x6-unorm': {
+ blockWidth: 8,
+ blockHeight: 6,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-8x6-unorm',
+ 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 },
+ baseFormat: 'astc-8x6-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-8x8-unorm': {
+ blockWidth: 8,
+ blockHeight: 8,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-8x8-unorm',
+ 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 },
+ baseFormat: 'astc-8x8-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-10x5-unorm': {
+ blockWidth: 10,
+ blockHeight: 5,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-10x5-unorm',
+ 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 },
+ baseFormat: 'astc-10x5-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-10x6-unorm': {
+ blockWidth: 10,
+ blockHeight: 6,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-10x6-unorm',
+ 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 },
+ baseFormat: 'astc-10x6-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-10x8-unorm': {
+ blockWidth: 10,
+ blockHeight: 8,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-10x8-unorm',
+ 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 },
+ baseFormat: 'astc-10x8-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-10x10-unorm': {
+ blockWidth: 10,
+ blockHeight: 10,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-10x10-unorm',
+ 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 },
+ baseFormat: 'astc-10x10-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-12x10-unorm': {
+ blockWidth: 12,
+ blockHeight: 10,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-12x10-unorm',
+ 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 },
+ baseFormat: 'astc-12x10-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ },
+
+ 'astc-12x12-unorm': {
+ blockWidth: 12,
+ blockHeight: 12,
+ color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ baseFormat: 'astc-12x12-unorm',
+ 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 },
+ baseFormat: 'astc-12x12-unorm',
+ get bytesPerBlock() {return this.color.bytes;}
+ }
+ }
+});
+
+// Definitions for use locally. To access the table entries, use `kTextureFormatInfo`.
+
+// MAINTENANCE_TODO: Consider generating the exports below programmatically by filtering the big list, instead
+// of using these local constants? Requires some type magic though.
+const kCompressedTextureFormatInfo = { ...kBCTextureFormatInfo, ...kETC2TextureFormatInfo, ...kASTCTextureFormatInfo };
+const kColorTextureFormatInfo = { ...kRegularTextureFormatInfo, ...kCompressedTextureFormatInfo };
+const kEncodableTextureFormatInfo = { ...kRegularTextureFormatInfo, ...kSizedDepthStencilFormatInfo };
+const kSizedTextureFormatInfo = { ...kRegularTextureFormatInfo, ...kSizedDepthStencilFormatInfo, ...kCompressedTextureFormatInfo };
+const kDepthStencilFormatInfo = { ...kSizedDepthStencilFormatInfo, ...kUnsizedDepthStencilFormatInfo };
+const kUncompressedTextureFormatInfo = { ...kRegularTextureFormatInfo, ...kSizedDepthStencilFormatInfo, ...kUnsizedDepthStencilFormatInfo };
+const kAllTextureFormatInfo = { ...kUncompressedTextureFormatInfo, ...kCompressedTextureFormatInfo };
+
+/** A "regular" texture format (uncompressed, sized, single-plane color formats). */
+
+/** A sized depth/stencil texture format. */
+
+/** An unsized depth/stencil texture format. */
+
+/** A compressed (block) texture format. */
+
+
+/** A color texture format (regular | compressed). */
+
+/** An encodable texture format (regular | sized depth/stencil). */
+
+/** A sized texture format (regular | sized depth/stencil | compressed). */
+
+/** A depth/stencil format (sized | unsized). */
+
+/** An uncompressed (block size 1x1) format (regular | depth/stencil). */
+
+
+export const kRegularTextureFormats = keysOf(kRegularTextureFormatInfo);
+export const kSizedDepthStencilFormats = keysOf(kSizedDepthStencilFormatInfo);
+export const kUnsizedDepthStencilFormats = keysOf(kUnsizedDepthStencilFormatInfo);
+export const kCompressedTextureFormats = keysOf(kCompressedTextureFormatInfo);
+
+export const kColorTextureFormats = keysOf(kColorTextureFormatInfo);
+export const kEncodableTextureFormats = keysOf(kEncodableTextureFormatInfo);
+export const kSizedTextureFormats = keysOf(kSizedTextureFormatInfo);
+export const kDepthStencilFormats = keysOf(kDepthStencilFormatInfo);
+export const kUncompressedTextureFormats = keysOf(kUncompressedTextureFormatInfo);
+export const kAllTextureFormats = keysOf(kAllTextureFormatInfo);
+
+// CompressedTextureFormat are unrenderable so filter from RegularTextureFormats for color targets is enough
+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. */
+
+
+
+
+
+
+
+
+
+
+/** Per GPUTextureFormat-per-aspect info for color aspects. */
+
+
+
+
+
+/** Per GPUTextureFormat-per-aspect info for depth aspects. */
+
+
+
+
+/** Per GPUTextureFormat-per-aspect info for stencil aspects. */
+
+
+
+
+
+/**
+ * Per-GPUTextureFormat info.
+ * 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.
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** Per-GPUTextureFormat info. */
+export const kTextureFormatInfo = {
+ ...kRegularTextureFormatInfo,
+ ...kSizedDepthStencilFormatInfo,
+ ...kUnsizedDepthStencilFormatInfo,
+ ...kBCTextureFormatInfo,
+ ...kETC2TextureFormatInfo,
+ ...kASTCTextureFormatInfo
+};
+
+/** Defining this variable verifies the type of kTextureFormatInfo2. It is not used. */
+
+const kTextureFormatInfo_TypeCheck =
+
+kTextureFormatInfo;
+
+/** List of all GPUTextureFormat values. */
+// MAINTENANCE_TODO: dedup with kAllTextureFormats
+export const kTextureFormats = keysOf(kAllTextureFormatInfo);
+
+/** Valid GPUTextureFormats for `copyExternalImageToTexture`, by spec. */
+export const kValidTextureFormatsForCopyE2T = [
+'r8unorm',
+'r16float',
+'r32float',
+'rg8unorm',
+'rg16float',
+'rg32float',
+'rgba8unorm',
+'rgba8unorm-srgb',
+'bgra8unorm',
+'bgra8unorm-srgb',
+'rgb10a2unorm',
+'rgba16float',
+'rgba32float'];
+
+
+//
+// Other related stuff
+//
+
+const kDepthStencilFormatCapabilityInBufferTextureCopy = {
+ // kUnsizedDepthStencilFormats
+ depth24plus: {
+ CopyB2T: [],
+ CopyT2B: [],
+ texelAspectSize: { 'depth-only': -1, 'stencil-only': -1 }
+ },
+ 'depth24plus-stencil8': {
+ CopyB2T: ['stencil-only'],
+ CopyT2B: ['stencil-only'],
+ texelAspectSize: { 'depth-only': -1, 'stencil-only': 1 }
+ },
+
+ // kSizedDepthStencilFormats
+ depth16unorm: {
+ CopyB2T: ['all', 'depth-only'],
+ CopyT2B: ['all', 'depth-only'],
+ texelAspectSize: { 'depth-only': 2, 'stencil-only': -1 }
+ },
+ depth32float: {
+ CopyB2T: [],
+ CopyT2B: ['all', 'depth-only'],
+ texelAspectSize: { 'depth-only': 4, 'stencil-only': -1 }
+ },
+ 'depth32float-stencil8': {
+ CopyB2T: ['stencil-only'],
+ CopyT2B: ['depth-only', 'stencil-only'],
+ texelAspectSize: { 'depth-only': 4, 'stencil-only': 1 }
+ },
+ stencil8: {
+ CopyB2T: ['all', 'stencil-only'],
+ CopyT2B: ['all', 'stencil-only'],
+ texelAspectSize: { 'depth-only': -1, 'stencil-only': 1 }
+ }
+};
+
+/** `kDepthStencilFormatResolvedAspect[format][aspect]` returns the aspect-specific format for a
+ * depth-stencil format, or `undefined` if the format doesn't have the aspect.
+ */
+export const kDepthStencilFormatResolvedAspect =
+
+
+
+{
+ // kUnsizedDepthStencilFormats
+ depth24plus: {
+ all: 'depth24plus',
+ 'depth-only': 'depth24plus',
+ 'stencil-only': undefined
+ },
+ 'depth24plus-stencil8': {
+ all: 'depth24plus-stencil8',
+ 'depth-only': 'depth24plus',
+ 'stencil-only': 'stencil8'
+ },
+
+ // kSizedDepthStencilFormats
+ depth16unorm: {
+ all: 'depth16unorm',
+ 'depth-only': 'depth16unorm',
+ 'stencil-only': undefined
+ },
+ depth32float: {
+ all: 'depth32float',
+ 'depth-only': 'depth32float',
+ 'stencil-only': undefined
+ },
+ 'depth32float-stencil8': {
+ all: 'depth32float-stencil8',
+ 'depth-only': 'depth32float',
+ 'stencil-only': 'stencil8'
+ },
+ stencil8: {
+ all: 'stencil8',
+ 'depth-only': undefined,
+ 'stencil-only': 'stencil8'
+ }
+};
+
+/**
+ * @returns the GPUTextureFormat corresponding to the @param aspect of @param format.
+ * This allows choosing the correct format for depth-stencil aspects when creating pipelines that
+ * will have to match the resolved format of views, or to get per-aspect information like the
+ * `blockByteSize`.
+ *
+ * Many helpers use an `undefined` `aspect` to means `'all'` so this is also the default for this
+ * function.
+ */
+export function resolvePerAspectFormat(
+format,
+aspect)
+{
+ if (aspect === 'all' || aspect === undefined) {
+ return format;
+ }
+ assert(!!kTextureFormatInfo[format].depth || !!kTextureFormatInfo[format].stencil);
+ const resolved = kDepthStencilFormatResolvedAspect[format][aspect ?? 'all'];
+ assert(resolved !== undefined);
+ return resolved;
+}
+
+/**
+ * Gets all copyable aspects for copies between texture and buffer for specified depth/stencil format and copy type, by spec.
+ */
+export function depthStencilFormatCopyableAspects(
+type,
+format)
+{
+ const appliedType = type === 'WriteTexture' ? 'CopyB2T' : type;
+ return kDepthStencilFormatCapabilityInBufferTextureCopy[format][appliedType];
+}
+
+/**
+ * Computes whether a copy between a depth/stencil texture aspect and a buffer is supported, by spec.
+ */
+export function depthStencilBufferTextureCopySupported(
+type,
+format,
+aspect)
+{
+ const supportedAspects = depthStencilFormatCopyableAspects(
+ type,
+ format
+ );
+ return supportedAspects.includes(aspect);
+}
+
+/**
+ * Returns the byte size of the depth or stencil aspect of the specified depth/stencil format,
+ * or -1 if none.
+ */
+export function depthStencilFormatAspectSize(
+format,
+aspect)
+{
+ const texelAspectSize =
+ kDepthStencilFormatCapabilityInBufferTextureCopy[format].texelAspectSize[aspect];
+ assert(texelAspectSize > 0);
+ return texelAspectSize;
+}
+
+/**
+ * Returns true iff a texture can be created with the provided GPUTextureDimension
+ * (defaulting to 2d) and GPUTextureFormat, by spec.
+ */
+export function textureDimensionAndFormatCompatible(
+dimension,
+format)
+{
+ const info = kAllTextureFormatInfo[format];
+ return !(
+ (dimension === '1d' || dimension === '3d') && (
+ info.blockWidth > 1 || info.depth || info.stencil));
+
+}
+
+/**
+ * Check if two formats are view format compatible.
+ *
+ * This function may need to be generalized to use `baseFormat` from `kTextureFormatInfo`.
+ */
+export function viewCompatible(a, b) {
+ return a === b || a + '-srgb' === b || b + '-srgb' === a;
+}
+
+export function getFeaturesForFormats(
+formats)
+{
+ return Array.from(new Set(formats.map((f) => f ? kTextureFormatInfo[f].feature : undefined)));
+}
+
+export function filterFormatsByFeature(
+feature,
+formats)
+{
+ return formats.filter((f) => f === undefined || kTextureFormatInfo[f].feature === feature);
+}
+
+export function isCompressedTextureFormat(format) {
+ return format in kCompressedTextureFormatInfo;
+}
+
+export const kFeaturesForFormats = getFeaturesForFormats(kTextureFormats);
+
+/**
+ * Given an array of texture formats return the number of bytes per sample.
+ */
+export function computeBytesPerSampleFromFormats(formats) {
+ let bytesPerSample = 0;
+ for (const format of formats) {
+ const info = kTextureFormatInfo[format];
+ const alignedBytesPerSample = align(bytesPerSample, info.colorRender.alignment);
+ bytesPerSample = alignedBytesPerSample + info.colorRender.byteCost;
+ }
+ return bytesPerSample;
+}
+
+/**
+ * Given an array of GPUColorTargetState return the number of bytes per sample
+ */
+export function computeBytesPerSample(targets) {
+ return computeBytesPerSampleFromFormats(targets.map(({ format }) => format));
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js
new file mode 100644
index 0000000000..2b589b9316
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js
@@ -0,0 +1,1681 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { Fixture,
+
+
+ SkipTestCase,
+ SubcaseBatchState } from
+
+
+'../common/framework/fixture.js';
+import { globalTestConfig } from '../common/framework/test_config.js';
+import {
+ assert,
+ makeValueTestVariant,
+ memcpy,
+ range,
+
+
+
+ unreachable } from
+'../common/util/util.js';
+
+import { getDefaultLimits, kQueryTypeInfo } from './capability_info.js';
+import {
+ kTextureFormatInfo,
+ kEncodableTextureFormats,
+ resolvePerAspectFormat,
+
+
+ isCompressedTextureFormat } from
+
+'./format_info.js';
+import { makeBufferWithContents } from './util/buffer.js';
+import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js';
+import { CommandBufferMaker } from './util/command_buffer_maker.js';
+
+import { DevicePool } from './util/device_pool.js';
+import { align, roundDown } from './util/math.js';
+import { physicalMipSizeFromTexture, virtualMipSize } from './util/texture/base.js';
+import {
+ bytesInACompleteRow,
+ getTextureCopyLayout,
+ getTextureSubCopyLayout } from
+
+'./util/texture/layout.js';
+import { kTexelRepresentationInfo } from './util/texture/texel_data.js';
+import { TexelView } from './util/texture/texel_view.js';
+import {
+
+
+
+ textureContentIsOKByT2B } from
+'./util/texture/texture_ok.js';
+import { createTextureFromTexelView, createTextureFromTexelViews } from './util/texture.js';
+import { reifyOrigin3D } from './util/unions.js';
+
+const devicePool = new DevicePool();
+
+// MAINTENANCE_TODO: When DevicePool becomes able to provide multiple devices at once, use the
+// usual one instead of a new one.
+const mismatchedDevicePool = new DevicePool();
+
+const kResourceStateValues = ['valid', 'invalid', 'destroyed'];
+
+export const kResourceStates = kResourceStateValues;
+
+/** Various "convenient" shorthands for GPUDeviceDescriptors for selectDevice functions. */
+
+
+
+
+
+
+export function initUncanonicalizedDeviceDescriptor(
+descriptor)
+{
+ if (typeof descriptor === 'string') {
+ return { requiredFeatures: [descriptor] };
+ } else if (descriptor instanceof Array) {
+ return {
+ requiredFeatures: descriptor.filter((f) => f !== undefined)
+ };
+ } else {
+ return descriptor;
+ }
+}
+
+export class GPUTestSubcaseBatchState extends SubcaseBatchState {
+ /** Provider for default device. */
+
+ /** Provider for mismatched device. */
+
+
+ async postInit() {
+ // Skip all subcases if there's no device.
+ await this.acquireProvider();
+ }
+
+ async finalize() {
+ await super.finalize();
+
+ // Ensure devicePool.release is called for both providers even if one rejects.
+ await Promise.all([
+ this.provider?.then((x) => devicePool.release(x)),
+ this.mismatchedProvider?.then((x) => devicePool.release(x))]
+ );
+ }
+
+ /** @internal MAINTENANCE_TODO: Make this not visible to test code? */
+ acquireProvider() {
+ if (this.provider === undefined) {
+ this.selectDeviceOrSkipTestCase(undefined);
+ }
+ assert(this.provider !== undefined);
+ return this.provider;
+ }
+
+ get isCompatibility() {
+ return globalTestConfig.compatibility;
+ }
+
+ getDefaultLimits() {
+ return getDefaultLimits(this.isCompatibility ? 'compatibility' : 'core');
+ }
+
+ /**
+ * Some tests or cases need particular feature flags or limits to be enabled.
+ * Call this function with a descriptor or feature name (or `undefined`) to select a
+ * GPUDevice with matching capabilities. If this isn't called, a default device is provided.
+ *
+ * If the request isn't supported, throws a SkipTestCase exception to skip the entire test case.
+ */
+ selectDeviceOrSkipTestCase(descriptor) {
+ assert(this.provider === undefined, "Can't selectDeviceOrSkipTestCase() multiple times");
+ this.provider = devicePool.acquire(
+ this.recorder,
+ initUncanonicalizedDeviceDescriptor(descriptor)
+ );
+ // Suppress uncaught promise rejection (we'll catch it later).
+ this.provider.catch(() => {});
+ }
+
+ /**
+ * Convenience function for {@link selectDeviceOrSkipTestCase}.
+ * Select a device with the features required by these texture format(s).
+ * If the device creation fails, then skip the test case.
+ */
+ selectDeviceForTextureFormatOrSkipTestCase(
+ formats)
+ {
+ if (!Array.isArray(formats)) {
+ formats = [formats];
+ }
+ const features = new Set();
+ for (const format of formats) {
+ if (format !== undefined) {
+ this.skipIfTextureFormatNotSupported(format);
+ features.add(kTextureFormatInfo[format].feature);
+ }
+ }
+
+ this.selectDeviceOrSkipTestCase(Array.from(features));
+ }
+
+ /**
+ * Convenience function for {@link selectDeviceOrSkipTestCase}.
+ * Select a device with the features required by these query type(s).
+ * If the device creation fails, then skip the test case.
+ */
+ selectDeviceForQueryTypeOrSkipTestCase(types) {
+ if (!Array.isArray(types)) {
+ types = [types];
+ }
+ const features = types.map((t) => kQueryTypeInfo[t].feature);
+ this.selectDeviceOrSkipTestCase(features);
+ }
+
+ /** @internal MAINTENANCE_TODO: Make this not visible to test code? */
+ acquireMismatchedProvider() {
+ return this.mismatchedProvider;
+ }
+
+ /**
+ * Some tests need a second device which is different from the first.
+ * This requests a second device so it will be available during the test. If it is not called,
+ * no second device will be available.
+ *
+ * If the request isn't supported, throws a SkipTestCase exception to skip the entire test case.
+ */
+ selectMismatchedDeviceOrSkipTestCase(descriptor) {
+ assert(
+ this.mismatchedProvider === undefined,
+ "Can't selectMismatchedDeviceOrSkipTestCase() multiple times"
+ );
+
+ this.mismatchedProvider = mismatchedDevicePool.acquire(
+ this.recorder,
+ initUncanonicalizedDeviceDescriptor(descriptor)
+ );
+ // Suppress uncaught promise rejection (we'll catch it later).
+ this.mismatchedProvider.catch(() => {});
+ }
+
+ /** Throws an exception marking the subcase as skipped. */
+ skip(msg) {
+ throw new SkipTestCase(msg);
+ }
+
+ /** Throws an exception making the subcase as skipped if condition is true */
+ skipIf(cond, msg = '') {
+ if (cond) {
+ this.skip(typeof msg === 'function' ? msg() : msg);
+ }
+ }
+
+ /**
+ * Skips test if any format is not supported.
+ */
+ skipIfTextureFormatNotSupported(...formats) {
+ if (this.isCompatibility) {
+ for (const format of formats) {
+ if (format === 'bgra8unorm-srgb') {
+ this.skip(`texture format '${format} is not supported`);
+ }
+ }
+ }
+ }
+
+ skipIfCopyTextureToTextureNotSupportedForFormat(...formats) {
+ if (this.isCompatibility) {
+ for (const format of formats) {
+ if (format && isCompressedTextureFormat(format)) {
+ this.skip(`copyTextureToTexture with ${format} is not supported`);
+ }
+ }
+ }
+ }
+
+ skipIfTextureViewDimensionNotSupported(...dimensions) {
+ if (this.isCompatibility) {
+ for (const dimension of dimensions) {
+ if (dimension === 'cube-array') {
+ this.skip(`texture view dimension '${dimension}' is not supported`);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Base fixture for WebGPU tests.
+ *
+ * This class is a Fixture + a getter that returns a GPUDevice
+ * as well as helpers that use that device.
+ */
+export class GPUTestBase extends Fixture {
+ static MakeSharedState(
+ recorder,
+ params)
+ {
+ return new GPUTestSubcaseBatchState(recorder, params);
+ }
+
+ // This must be overridden in derived classes
+ get device() {
+ unreachable();
+ return null;
+ }
+
+ /** GPUQueue for the test to use. (Same as `t.device.queue`.) */
+ get queue() {
+ return this.device.queue;
+ }
+
+ get isCompatibility() {
+ return globalTestConfig.compatibility;
+ }
+
+ getDefaultLimits() {
+ return getDefaultLimits(this.isCompatibility ? 'compatibility' : 'core');
+ }
+
+ getDefaultLimit(limit) {
+ return this.getDefaultLimits()[limit].default;
+ }
+
+ makeLimitVariant(limit, variant) {
+ return makeValueTestVariant(this.device.limits[limit], variant);
+ }
+
+ canCallCopyTextureToBufferWithTextureFormat(format) {
+ return !this.isCompatibility || !isCompressedTextureFormat(format);
+ }
+
+ /** Snapshot a GPUBuffer's contents, returning a new GPUBuffer with the `MAP_READ` usage. */
+ createCopyForMapRead(src, srcOffset, size) {
+ assert(srcOffset % 4 === 0);
+ assert(size % 4 === 0);
+
+ const dst = this.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(dst);
+
+ const c = this.device.createCommandEncoder();
+ c.copyBufferToBuffer(src, srcOffset, dst, 0, size);
+ this.queue.submit([c.finish()]);
+
+ return dst;
+ }
+
+ /**
+ * Offset and size passed to createCopyForMapRead must be divisible by 4. For that
+ * we might need to copy more bytes from the buffer than we want to map.
+ * begin and end values represent the part of the copied buffer that stores the contents
+ * we initially wanted to map.
+ * The copy will not cause an OOB error because the buffer size must be 4-aligned.
+ */
+ createAlignedCopyForMapRead(
+ src,
+ size,
+ offset)
+ {
+ const alignedOffset = roundDown(offset, 4);
+ const subarrayByteStart = offset - alignedOffset;
+ const alignedSize = align(size + subarrayByteStart, 4);
+ const mappable = this.createCopyForMapRead(src, alignedOffset, alignedSize);
+ return { mappable, subarrayByteStart };
+ }
+
+ /**
+ * Snapshot the current contents of a range of a GPUBuffer, and return them as a TypedArray.
+ * Also provides a cleanup() function to unmap and destroy the staging buffer.
+ */
+ async readGPUBufferRangeTyped(
+ src,
+ {
+ srcByteOffset = 0,
+ method = 'copy',
+ type,
+ typedLength
+
+
+
+
+
+ })
+ {
+ assert(
+ srcByteOffset % type.BYTES_PER_ELEMENT === 0,
+ 'srcByteOffset must be a multiple of BYTES_PER_ELEMENT'
+ );
+
+ const byteLength = typedLength * type.BYTES_PER_ELEMENT;
+ let mappable;
+ let mapOffset, mapSize, subarrayByteStart;
+ if (method === 'copy') {
+ ({ mappable, subarrayByteStart } = this.createAlignedCopyForMapRead(
+ src,
+ byteLength,
+ srcByteOffset
+ ));
+ } else if (method === 'map') {
+ mappable = src;
+ mapOffset = roundDown(srcByteOffset, 8);
+ mapSize = align(byteLength, 4);
+ subarrayByteStart = srcByteOffset - mapOffset;
+ } else {
+ unreachable();
+ }
+
+ assert(subarrayByteStart % type.BYTES_PER_ELEMENT === 0);
+ const subarrayStart = subarrayByteStart / type.BYTES_PER_ELEMENT;
+
+ // 2. Map the staging buffer, and create the TypedArray from it.
+ await mappable.mapAsync(GPUMapMode.READ, mapOffset, mapSize);
+ const mapped = new type(mappable.getMappedRange(mapOffset, mapSize));
+ const data = mapped.subarray(subarrayStart, typedLength);
+
+ return {
+ data,
+ cleanup() {
+ mappable.unmap();
+ mappable.destroy();
+ }
+ };
+ }
+
+ /**
+ * Skips test if any format is not supported.
+ */
+ skipIfTextureFormatNotSupported(...formats) {
+ if (this.isCompatibility) {
+ for (const format of formats) {
+ if (format === 'bgra8unorm-srgb') {
+ this.skip(`texture format '${format} is not supported`);
+ }
+ }
+ }
+ }
+
+ skipIfTextureViewDimensionNotSupported(...dimensions) {
+ if (this.isCompatibility) {
+ for (const dimension of dimensions) {
+ if (dimension === 'cube-array') {
+ this.skip(`texture view dimension '${dimension}' is not supported`);
+ }
+ }
+ }
+ }
+
+ skipIfCopyTextureToTextureNotSupportedForFormat(...formats) {
+ if (this.isCompatibility) {
+ for (const format of formats) {
+ if (format && isCompressedTextureFormat(format)) {
+ this.skip(`copyTextureToTexture with ${format} is not supported`);
+ }
+ }
+ }
+ }
+
+ /**
+ * Expect a GPUBuffer's contents to pass the provided check.
+ *
+ * A library of checks can be found in {@link webgpu/util/check_contents}.
+ */
+ expectGPUBufferValuesPassCheck(
+ src,
+ check,
+ {
+ srcByteOffset = 0,
+ type,
+ typedLength,
+ method = 'copy',
+ mode = 'fail'
+
+
+
+
+
+
+ })
+ {
+ const readbackPromise = this.readGPUBufferRangeTyped(src, {
+ srcByteOffset,
+ type,
+ typedLength,
+ method
+ });
+ this.eventualAsyncExpectation(async (niceStack) => {
+ const readback = await readbackPromise;
+ this.expectOK(check(readback.data), { mode, niceStack });
+ readback.cleanup();
+ });
+ }
+
+ /**
+ * Expect a GPUBuffer's contents to equal the values in the provided TypedArray.
+ */
+ expectGPUBufferValuesEqual(
+ src,
+ expected,
+ srcByteOffset = 0,
+ { method = 'copy', mode = 'fail' } = {})
+ {
+ this.expectGPUBufferValuesPassCheck(src, (a) => checkElementsEqual(a, expected), {
+ srcByteOffset,
+ type: expected.constructor,
+ typedLength: expected.length,
+ method,
+ mode
+ });
+ }
+
+ /**
+ * Expect a buffer to consist exclusively of rows of some repeated expected value. The size of
+ * `expectedValue` must be 1, 2, or any multiple of 4 bytes. Rows in the buffer are expected to be
+ * zero-padded out to `bytesPerRow`. `minBytesPerRow` is the number of bytes per row that contain
+ * actual (non-padding) data and must be an exact multiple of the byte-length of `expectedValue`.
+ */
+ expectGPUBufferRepeatsSingleValue(
+ buffer,
+ {
+ expectedValue,
+ numRows,
+ minBytesPerRow,
+ bytesPerRow
+
+
+
+
+
+ })
+ {
+ const valueSize = expectedValue.byteLength;
+ assert(valueSize === 1 || valueSize === 2 || valueSize % 4 === 0);
+ assert(minBytesPerRow % valueSize === 0);
+ assert(bytesPerRow % 4 === 0);
+
+ // If the buffer is small enough, just generate the full expected buffer contents and check
+ // against them on the CPU.
+ const kMaxBufferSizeToCheckOnCpu = 256 * 1024;
+ const bufferSize = bytesPerRow * (numRows - 1) + minBytesPerRow;
+ if (bufferSize <= kMaxBufferSizeToCheckOnCpu) {
+ const valueBytes = Array.from(new Uint8Array(expectedValue));
+ const rowValues = new Array(minBytesPerRow / valueSize).fill(valueBytes);
+ const rowBytes = new Uint8Array([].concat(...rowValues));
+ const expectedContents = new Uint8Array(bufferSize);
+ range(numRows, (row) => expectedContents.set(rowBytes, row * bytesPerRow));
+ this.expectGPUBufferValuesEqual(buffer, expectedContents);
+ return;
+ }
+
+ // Copy into a buffer suitable for STORAGE usage.
+ const storageBuffer = this.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(storageBuffer);
+
+ // This buffer conveys the data we expect to see for a single value read. Since we read 32 bits at
+ // a time, for values smaller than 32 bits we pad this expectation with repeated value data, or
+ // with zeroes if the width of a row in the buffer is less than 4 bytes. For value sizes larger
+ // than 32 bits, we assume they're a multiple of 32 bits and expect to read exact matches of
+ // `expectedValue` as-is.
+ const expectedDataSize = Math.max(4, valueSize);
+ const expectedDataBuffer = this.device.createBuffer({
+ size: expectedDataSize,
+ usage: GPUBufferUsage.STORAGE,
+ mappedAtCreation: true
+ });
+ this.trackForCleanup(expectedDataBuffer);
+ const expectedData = new Uint32Array(expectedDataBuffer.getMappedRange());
+ if (valueSize === 1) {
+ const value = new Uint8Array(expectedValue)[0];
+ const values = new Array(Math.min(4, minBytesPerRow)).fill(value);
+ const padding = new Array(Math.max(0, 4 - values.length)).fill(0);
+ const expectedBytes = new Uint8Array(expectedData.buffer);
+ expectedBytes.set([...values, ...padding]);
+ } else if (valueSize === 2) {
+ const value = new Uint16Array(expectedValue)[0];
+ const expectedWords = new Uint16Array(expectedData.buffer);
+ expectedWords.set([value, minBytesPerRow > 2 ? value : 0]);
+ } else {
+ expectedData.set(new Uint32Array(expectedValue));
+ }
+ expectedDataBuffer.unmap();
+
+ // The output buffer has one 32-bit entry per buffer row. An entry's value will be 1 if every
+ // read from the corresponding row matches the expected data derived above, or 0 otherwise.
+ const resultBuffer = this.device.createBuffer({
+ size: numRows * 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ this.trackForCleanup(resultBuffer);
+
+ const readsPerRow = Math.ceil(minBytesPerRow / expectedDataSize);
+ const reducer = `
+ struct Buffer { data: array<u32>, };
+ @group(0) @binding(0) var<storage, read> expected: Buffer;
+ @group(0) @binding(1) var<storage, read> in: Buffer;
+ @group(0) @binding(2) var<storage, read_write> out: Buffer;
+ @compute @workgroup_size(1) fn reduce(
+ @builtin(global_invocation_id) id: vec3<u32>) {
+ let rowBaseIndex = id.x * ${bytesPerRow / 4}u;
+ let readSize = ${expectedDataSize / 4}u;
+ out.data[id.x] = 1u;
+ for (var i: u32 = 0u; i < ${readsPerRow}u; i = i + 1u) {
+ let elementBaseIndex = rowBaseIndex + i * readSize;
+ for (var j: u32 = 0u; j < readSize; j = j + 1u) {
+ if (in.data[elementBaseIndex + j] != expected.data[j]) {
+ out.data[id.x] = 0u;
+ return;
+ }
+ }
+ }
+ }
+ `;
+
+ const pipeline = this.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: this.device.createShaderModule({ code: reducer }),
+ entryPoint: 'reduce'
+ }
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: expectedDataBuffer } },
+ { binding: 1, resource: { buffer: storageBuffer } },
+ { binding: 2, resource: { buffer: resultBuffer } }]
+
+ });
+
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyBufferToBuffer(buffer, 0, storageBuffer, 0, bufferSize);
+ const pass = commandEncoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(numRows);
+ pass.end();
+ this.device.queue.submit([commandEncoder.finish()]);
+
+ const expectedResults = new Array(numRows).fill(1);
+ this.expectGPUBufferValuesEqual(resultBuffer, new Uint32Array(expectedResults));
+ }
+
+ // MAINTENANCE_TODO: add an expectContents for textures, which logs data: uris on failure
+
+ /**
+ * Expect an entire GPUTexture to have a single color at the given mip level (defaults to 0).
+ * MAINTENANCE_TODO: Remove this and/or replace it with a helper in TextureTestMixin.
+ */
+ expectSingleColor(
+ src,
+ format,
+ {
+ size,
+ exp,
+ dimension = '2d',
+ slice = 0,
+ layout
+
+
+
+
+
+
+ })
+ {
+ assert(
+ slice === 0 || dimension === '2d',
+ 'texture slices are only implemented for 2d textures'
+ );
+
+ format = resolvePerAspectFormat(format, layout?.aspect);
+ const { byteLength, minBytesPerRow, bytesPerRow, rowsPerImage, mipSize } = getTextureCopyLayout(
+ format,
+ dimension,
+ size,
+ layout
+ );
+ // MAINTENANCE_TODO: getTextureCopyLayout does not return the proper size for array textures,
+ // i.e. it will leave the z/depth value as is instead of making it 1 when dealing with 2d
+ // texture arrays. Since we are passing in the dimension, we should update it to return the
+ // corrected size.
+ const copySize = [
+ mipSize[0],
+ dimension !== '1d' ? mipSize[1] : 1,
+ dimension === '3d' ? mipSize[2] : 1];
+
+
+ const rep = kTexelRepresentationInfo[format];
+ const expectedTexelData = rep.pack(rep.encode(exp));
+
+ const buffer = this.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(buffer);
+
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyTextureToBuffer(
+ {
+ texture: src,
+ mipLevel: layout?.mipLevel,
+ origin: { x: 0, y: 0, z: slice },
+ aspect: layout?.aspect
+ },
+ { buffer, bytesPerRow, rowsPerImage },
+ copySize
+ );
+ this.queue.submit([commandEncoder.finish()]);
+
+ this.expectGPUBufferRepeatsSingleValue(buffer, {
+ expectedValue: expectedTexelData,
+ numRows: rowsPerImage * copySize[2],
+ minBytesPerRow,
+ bytesPerRow
+ });
+ }
+
+ /**
+ * Return a GPUBuffer that data are going to be written into.
+ * MAINTENANCE_TODO: Remove this once expectSinglePixelBetweenTwoValuesIn2DTexture is removed.
+ */
+ readSinglePixelFrom2DTexture(
+ src,
+ format,
+ { x, y },
+ { slice = 0, layout })
+ {
+ const { byteLength, bytesPerRow, rowsPerImage } = getTextureSubCopyLayout(
+ format,
+ [1, 1],
+ layout
+ );
+ const buffer = this.device.createBuffer({
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(buffer);
+
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyTextureToBuffer(
+ { texture: src, mipLevel: layout?.mipLevel, origin: { x, y, z: slice } },
+ { buffer, bytesPerRow, rowsPerImage },
+ [1, 1]
+ );
+ this.queue.submit([commandEncoder.finish()]);
+
+ return buffer;
+ }
+
+ /**
+ * Take a single pixel of a 2D texture, interpret it using a TypedArray of the `expected` type,
+ * and expect each value in that array to be between the corresponding "expected" values
+ * (either `a[i] <= actual[i] <= b[i]` or `a[i] >= actual[i] => b[i]`).
+ * MAINTENANCE_TODO: Remove this once there is a way to deal with undefined lerp-ed values.
+ */
+ expectSinglePixelBetweenTwoValuesIn2DTexture(
+ src,
+ format,
+ { x, y },
+ {
+ exp,
+ slice = 0,
+ layout,
+ generateWarningOnly = false,
+ checkElementsBetweenFn = (act, [a, b]) => checkElementsBetween(act, [(i) => a[i], (i) => b[i]])
+
+
+
+
+
+
+
+
+
+ })
+ {
+ assert(exp[0].constructor === exp[1].constructor);
+ const constructor = exp[0].constructor;
+ assert(exp[0].length === exp[1].length);
+ const typedLength = exp[0].length;
+
+ const buffer = this.readSinglePixelFrom2DTexture(src, format, { x, y }, { slice, layout });
+ this.expectGPUBufferValuesPassCheck(buffer, (a) => checkElementsBetweenFn(a, exp), {
+ type: constructor,
+ typedLength,
+ mode: generateWarningOnly ? 'warn' : 'fail'
+ });
+ }
+
+ /**
+ * 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
+ * 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.
+ *
+ * @returns storage buffer containing the copied value from the texture.
+ */
+ copySinglePixelTextureToBufferUsingComputePass(
+ type,
+ componentCount,
+ textureView,
+ sampleCount)
+ {
+ const textureSrcCode =
+ sampleCount === 1 ?
+ `@group(0) @binding(0) var src: texture_2d<${type}>;` :
+ `@group(0) @binding(0) var src: texture_multisampled_2d<${type}>;`;
+ const code = `
+ struct Buffer {
+ data: array<${type}>,
+ };
+
+ ${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};
+ sampleIndex = sampleIndex + 1) {
+ let o = sampleIndex * ${componentCount};
+ let v = textureLoad(src, coord, sampleIndex);
+ for (var component = 0; component < ${componentCount}; component = component + 1) {
+ dst.data[o + component] = v[component];
+ }
+ }
+ }
+ `;
+ const computePipeline = this.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: this.device.createShaderModule({
+ code
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const storageBuffer = this.device.createBuffer({
+ size: sampleCount * type.size * componentCount,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
+ });
+ this.trackForCleanup(storageBuffer);
+
+ const uniformBindGroup = this.device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: textureView
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: storageBuffer
+ }
+ }]
+
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(computePipeline);
+ pass.setBindGroup(0, uniformBindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ return storageBuffer;
+ }
+
+ /**
+ * Expect the specified WebGPU error to be generated when running the provided function.
+ */
+ expectGPUError(filter, fn, shouldError = true) {
+ // If no error is expected, we let the scope surrounding the test catch it.
+ if (!shouldError) {
+ return fn();
+ }
+
+ this.device.pushErrorScope(filter);
+ const returnValue = fn();
+ const promise = this.device.popErrorScope();
+
+ this.eventualAsyncExpectation(async (niceStack) => {
+ const error = await promise;
+
+ let failed = false;
+ switch (filter) {
+ case 'out-of-memory':
+ failed = !(error instanceof GPUOutOfMemoryError);
+ break;
+ case 'validation':
+ failed = !(error instanceof GPUValidationError);
+ break;
+ }
+
+ if (failed) {
+ niceStack.message = `Expected ${filter} error`;
+ this.rec.expectationFailed(niceStack);
+ } else {
+ niceStack.message = `Captured ${filter} error`;
+ if (error instanceof GPUValidationError) {
+ niceStack.message += ` - ${error.message}`;
+ }
+ this.rec.debug(niceStack);
+ }
+ });
+
+ return returnValue;
+ }
+
+ /**
+ * Expect a validation error inside the callback.
+ *
+ * Tests should always do just one WebGPU call in the callback, to make sure that's what's tested.
+ */
+ expectValidationError(fn, shouldError = true) {
+ // If no error is expected, we let the scope surrounding the test catch it.
+ if (shouldError) {
+ this.device.pushErrorScope('validation');
+ }
+
+ // Note: A return value is not allowed for the callback function. This is to avoid confusion
+ // about what the actual behavior would be; either of the following could be reasonable:
+ // - Make expectValidationError async, and have it await on fn(). This causes an async split
+ // between pushErrorScope and popErrorScope, so if the caller doesn't `await` on
+ // expectValidationError (either accidentally or because it doesn't care to do so), then
+ // other test code will be (nondeterministically) caught by the error scope.
+ // - Make expectValidationError NOT await fn(), but just execute its first block (until the
+ // first await) and return the return value (a Promise). This would be confusing because it
+ // would look like the error scope includes the whole async function, but doesn't.
+ // If we do decide we need to return a value, we should use the latter semantic.
+ const returnValue = fn();
+ assert(
+ returnValue === undefined,
+ 'expectValidationError callback should not return a value (or be async)'
+ );
+
+ if (shouldError) {
+ const promise = this.device.popErrorScope();
+
+ this.eventualAsyncExpectation(async (niceStack) => {
+ const gpuValidationError = await promise;
+ if (!gpuValidationError) {
+ niceStack.message = 'Validation succeeded unexpectedly.';
+ this.rec.validationFailed(niceStack);
+ } else if (gpuValidationError instanceof GPUValidationError) {
+ niceStack.message = `Validation failed, as expected - ${gpuValidationError.message}`;
+ this.rec.debug(niceStack);
+ }
+ });
+ }
+ }
+
+ /**
+ * Create a GPUBuffer with the specified contents and usage.
+ *
+ * MAINTENANCE_TODO: Several call sites would be simplified if this took ArrayBuffer as well.
+ */
+ makeBufferWithContents(dataArray, usage) {
+ return this.trackForCleanup(makeBufferWithContents(this.device, dataArray, usage));
+ }
+
+ /**
+ * Returns a GPUCommandEncoder, GPUComputePassEncoder, GPURenderPassEncoder, or
+ * GPURenderBundleEncoder, and a `finish` method returning a GPUCommandBuffer.
+ * Allows testing methods which have the same signature across multiple encoder interfaces.
+ *
+ * @example
+ * ```
+ * g.test('popDebugGroup')
+ * .params(u => u.combine('encoderType', kEncoderTypes))
+ * .fn(t => {
+ * const { encoder, finish } = t.createEncoder(t.params.encoderType);
+ * encoder.popDebugGroup();
+ * });
+ *
+ * g.test('writeTimestamp')
+ * .params(u => u.combine('encoderType', ['non-pass', 'compute pass', 'render pass'] as const)
+ * .fn(t => {
+ * const { encoder, finish } = t.createEncoder(t.params.encoderType);
+ * // Encoder type is inferred, so `writeTimestamp` can be used even though it doesn't exist
+ * // on GPURenderBundleEncoder.
+ * encoder.writeTimestamp(args);
+ * });
+ * ```
+ */
+ createEncoder(
+ encoderType,
+ {
+ attachmentInfo,
+ occlusionQuerySet
+
+
+
+ } = {})
+ {
+ const fullAttachmentInfo = {
+ // Defaults if not overridden:
+ colorFormats: ['rgba8unorm'],
+ sampleCount: 1,
+ // Passed values take precedent.
+ ...attachmentInfo
+ };
+
+ switch (encoderType) {
+ case 'non-pass':{
+ const encoder = this.device.createCommandEncoder();
+
+ return new CommandBufferMaker(this, encoder, () => {
+ return encoder.finish();
+ });
+ }
+ case 'render bundle':{
+ const device = this.device;
+ const rbEncoder = device.createRenderBundleEncoder(fullAttachmentInfo);
+ const pass = this.createEncoder('render pass', { attachmentInfo });
+
+ return new CommandBufferMaker(this, rbEncoder, () => {
+ pass.encoder.executeBundles([rbEncoder.finish()]);
+ return pass.finish();
+ });
+ }
+ case 'compute pass':{
+ const commandEncoder = this.device.createCommandEncoder();
+ const encoder = commandEncoder.beginComputePass();
+
+ return new CommandBufferMaker(this, encoder, () => {
+ encoder.end();
+ return commandEncoder.finish();
+ });
+ }
+ case 'render pass':{
+ const makeAttachmentView = (format) =>
+ this.trackForCleanup(
+ this.device.createTexture({
+ size: [16, 16, 1],
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: fullAttachmentInfo.sampleCount
+ })
+ ).createView();
+
+ let depthStencilAttachment = undefined;
+ if (fullAttachmentInfo.depthStencilFormat !== undefined) {
+ depthStencilAttachment = {
+ view: makeAttachmentView(fullAttachmentInfo.depthStencilFormat),
+ depthReadOnly: fullAttachmentInfo.depthReadOnly,
+ stencilReadOnly: fullAttachmentInfo.stencilReadOnly
+ };
+ if (
+ kTextureFormatInfo[fullAttachmentInfo.depthStencilFormat].depth &&
+ !fullAttachmentInfo.depthReadOnly)
+ {
+ depthStencilAttachment.depthClearValue = 0;
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'discard';
+ }
+ if (
+ kTextureFormatInfo[fullAttachmentInfo.depthStencilFormat].stencil &&
+ !fullAttachmentInfo.stencilReadOnly)
+ {
+ depthStencilAttachment.stencilClearValue = 1;
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'discard';
+ }
+ }
+ const passDesc = {
+ colorAttachments: Array.from(fullAttachmentInfo.colorFormats, (format) =>
+ format ?
+ {
+ view: makeAttachmentView(format),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ } :
+ null
+ ),
+ depthStencilAttachment,
+ occlusionQuerySet
+ };
+
+ const commandEncoder = this.device.createCommandEncoder();
+ const encoder = commandEncoder.beginRenderPass(passDesc);
+ return new CommandBufferMaker(this, encoder, () => {
+ encoder.end();
+ return commandEncoder.finish();
+ });
+ }
+ }
+ unreachable();
+ }
+}
+
+/**
+ * Fixture for WebGPU tests that uses a DeviceProvider
+ */
+export class GPUTest extends GPUTestBase {
+ // Should never be undefined in a test. If it is, init() must not have run/finished.
+
+
+
+ async init() {
+ await super.init();
+
+ this.provider = await this.sharedState.acquireProvider();
+ this.mismatchedProvider = await this.sharedState.acquireMismatchedProvider();
+ }
+
+ /**
+ * GPUDevice for the test to use.
+ */
+ get device() {
+ assert(this.provider !== undefined, 'internal error: GPUDevice missing?');
+ return this.provider.device;
+ }
+
+ /**
+ * GPUDevice for tests requiring a second device different from the default one,
+ * e.g. for creating objects for by device_mismatch validation tests.
+ */
+ get mismatchedDevice() {
+ assert(
+ this.mismatchedProvider !== undefined,
+ 'selectMismatchedDeviceOrSkipTestCase was not called in beforeAllSubcases'
+ );
+ return this.mismatchedProvider.device;
+ }
+
+ /**
+ * Expects that the device should be lost for a particular reason at the teardown of the test.
+ */
+ expectDeviceLost(reason) {
+ assert(this.provider !== undefined, 'internal error: GPUDevice missing?');
+ this.provider.expectDeviceLost(reason);
+ }
+}
+
+/**
+ * Texture expectation mixin can be applied on top of GPUTest to add texture
+ * related expectation helpers.
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+const s_deviceToResourcesMap = new WeakMap();
+
+/**
+ * Gets a (cached) pipeline to render a texture to an rgba8unorm texture
+ */
+function getPipelineToRenderTextureToRGB8UnormTexture(device) {
+ if (!s_deviceToResourcesMap.has(device)) {
+ const module = device.createShaderModule({
+ code: `
+ struct VSOutput {
+ @builtin(position) position: vec4f,
+ @location(0) texcoord: vec2f,
+ };
+
+ @vertex fn vs(
+ @builtin(vertex_index) vertexIndex : u32
+ ) -> VSOutput {
+ let pos = array(
+ vec2f(-1, -1),
+ vec2f(-1, 3),
+ vec2f( 3, -1),
+ );
+
+ var vsOutput: VSOutput;
+
+ let xy = pos[vertexIndex];
+
+ vsOutput.position = vec4f(xy, 0.0, 1.0);
+ vsOutput.texcoord = xy * vec2f(0.5, -0.5) + vec2f(0.5);
+
+ return vsOutput;
+ }
+
+ @group(0) @binding(0) var ourSampler: sampler;
+ @group(0) @binding(1) var ourTexture: texture_2d<f32>;
+
+ @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {
+ return textureSample(ourTexture, ourSampler, fsInput.texcoord);
+ }
+ `
+ });
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: 'rgba8unorm' }]
+ }
+ });
+ s_deviceToResourcesMap.set(device, { pipeline });
+ }
+ const { pipeline } = s_deviceToResourcesMap.get(device);
+ return pipeline;
+}
+
+
+
+
+
+
+
+export function TextureTestMixin(
+Base)
+{
+ class TextureExpectations extends
+ Base
+
+ {
+ createTextureFromTexelView(
+ texelView,
+ desc)
+ {
+ return this.trackForCleanup(createTextureFromTexelView(this.device, texelView, desc));
+ }
+
+ createTextureFromTexelViewsMultipleMipmaps(
+ texelViews,
+ desc)
+ {
+ return this.trackForCleanup(createTextureFromTexelViews(this.device, texelViews, desc));
+ }
+
+ expectTexelViewComparisonIsOkInTexture(
+ src,
+ exp,
+ size,
+ comparisonOptions = {
+ maxIntDiff: 0,
+ maxDiffULPsForNormFormat: 1,
+ maxDiffULPsForFloatFormat: 1
+ })
+ {
+ this.eventualExpectOK(
+ textureContentIsOKByT2B(this, src, size, { expTexelView: exp }, comparisonOptions)
+ );
+ }
+
+ expectSinglePixelComparisonsAreOkInTexture(
+ src,
+ exp,
+ comparisonOptions = {
+ maxIntDiff: 0,
+ maxDiffULPsForNormFormat: 1,
+ maxDiffULPsForFloatFormat: 1
+ })
+ {
+ assert(exp.length > 0, 'must specify at least one pixel comparison');
+ assert(
+ kEncodableTextureFormats.includes(src.texture.format),
+ () => `${src.texture.format} is not an encodable format`
+ );
+ const lowerCorner = [src.texture.width, src.texture.height, src.texture.depthOrArrayLayers];
+ const upperCorner = [0, 0, 0];
+ const expMap = new Map();
+ const coords = [];
+ for (const e of exp) {
+ const coord = reifyOrigin3D(e.coord);
+ const coordKey = JSON.stringify(coord);
+ coords.push(coord);
+
+ // Compute the minimum sub-rect that encompasses all the pixel comparisons. The
+ // `lowerCorner` will become the origin, and the `upperCorner` will be used to compute the
+ // size.
+ lowerCorner[0] = Math.min(lowerCorner[0], coord.x);
+ lowerCorner[1] = Math.min(lowerCorner[1], coord.y);
+ lowerCorner[2] = Math.min(lowerCorner[2], coord.z);
+ upperCorner[0] = Math.max(upperCorner[0], coord.x);
+ upperCorner[1] = Math.max(upperCorner[1], coord.y);
+ upperCorner[2] = Math.max(upperCorner[2], coord.z);
+
+ // Build a sparse map of the coordinates to the expected colors for the texel view.
+ assert(
+ !expMap.has(coordKey),
+ () => `duplicate pixel expectation at coordinate (${coord.x},${coord.y},${coord.z})`
+ );
+ expMap.set(coordKey, e.exp);
+ }
+ const size = [
+ upperCorner[0] - lowerCorner[0] + 1,
+ upperCorner[1] - lowerCorner[1] + 1,
+ upperCorner[2] - lowerCorner[2] + 1];
+
+ let expTexelView;
+ if (Symbol.iterator in exp[0].exp) {
+ expTexelView = TexelView.fromTexelsAsBytes(
+ src.texture.format,
+ (coord) => {
+ const res = expMap.get(JSON.stringify(coord));
+ assert(
+ res !== undefined,
+ () => `invalid coordinate (${coord.x},${coord.y},${coord.z}) in sparse texel view`
+ );
+ return res;
+ }
+ );
+ } else {
+ expTexelView = TexelView.fromTexelsAsColors(
+ src.texture.format,
+ (coord) => {
+ const res = expMap.get(JSON.stringify(coord));
+ assert(
+ res !== undefined,
+ () => `invalid coordinate (${coord.x},${coord.y},${coord.z}) in sparse texel view`
+ );
+ return res;
+ }
+ );
+ }
+ const coordsF = function* () {
+ for (const coord of coords) {
+ yield coord;
+ }
+ }();
+
+ this.eventualExpectOK(
+ textureContentIsOKByT2B(
+ this,
+ { ...src, origin: reifyOrigin3D(lowerCorner) },
+ size,
+ { expTexelView },
+ comparisonOptions,
+ coordsF
+ )
+ );
+ }
+
+ expectTexturesToMatchByRendering(
+ actualTexture,
+ expectedTexture,
+ mipLevel,
+ origin,
+ size)
+ {
+ // 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 readbackPromisesPerTexturePerLayer = [actualTexture, expectedTexture].map(
+ (texture, ndx) => {
+ const attachmentSize = virtualMipSize('2d', [texture.width, texture.height, 1], mipLevel);
+ const attachment = this.device.createTexture({
+ label: `readback${ndx}`,
+ size: attachmentSize,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ this.trackForCleanup(attachment);
+
+ const sampler = this.device.createSampler();
+
+ const numLayers = texture.depthOrArrayLayers;
+ const readbackPromisesPerLayer = [];
+ for (let layer = 0; layer < numLayers; ++layer) {
+ 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'
+ })
+ }]
+
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachment.createView(),
+ clearValue: [0.5, 0.5, 0.5, 0.5],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.draw(3);
+ pass.end();
+ this.queue.submit([encoder.finish()]);
+
+ const buffer = this.copyWholeTextureToNewBufferSimple(attachment, 0);
+
+ readbackPromisesPerLayer.push(
+ this.readGPUBufferRangeTyped(buffer, {
+ type: Uint8Array,
+ typedLength: buffer.size
+ })
+ );
+ }
+ return readbackPromisesPerLayer;
+ }
+ );
+
+ this.eventualAsyncExpectation(async (niceStack) => {
+ const readbacksPerTexturePerLayer = [];
+
+ // Wait for all buffers to be ready
+ for (const readbackPromises of readbackPromisesPerTexturePerLayer) {
+ readbacksPerTexturePerLayer.push(await Promise.all(readbackPromises));
+ }
+
+ function arrayNotAllTheSameValue(arr, msg) {
+ const first = arr[0];
+ return arr.length <= 1 || arr.findIndex((v) => v !== first) >= 0 ?
+ undefined :
+ Error(`array is entirely ${first} so likely nothing was tested: ${msg || ''}`);
+ }
+
+ // Compare each layer of each texture as read from buffer.
+ const [actualReadbacksPerLayer, expectedReadbacksPerLayer] = readbacksPerTexturePerLayer;
+ for (let layer = 0; layer < actualReadbacksPerLayer.length; ++layer) {
+ const actualReadback = actualReadbacksPerLayer[layer];
+ const expectedReadback = expectedReadbacksPerLayer[layer];
+ const sameOk =
+ size.width === 0 ||
+ size.height === 0 ||
+ layer < origin.z ||
+ layer >= origin.z + size.depthOrArrayLayers;
+ this.expectOK(
+ sameOk ? undefined : arrayNotAllTheSameValue(actualReadback.data, 'actualTexture')
+ );
+ this.expectOK(
+ sameOk ? undefined : arrayNotAllTheSameValue(expectedReadback.data, 'expectedTexture')
+ );
+ this.expectOK(checkElementsEqual(actualReadback.data, expectedReadback.data), {
+ mode: 'fail',
+ niceStack
+ });
+ actualReadback.cleanup();
+ expectedReadback.cleanup();
+ }
+ });
+ }
+
+ copyWholeTextureToNewBufferSimple(texture, mipLevel) {
+ const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[texture.format];
+ const mipSize = physicalMipSizeFromTexture(texture, mipLevel);
+ assert(bytesPerBlock !== undefined);
+
+ const blocksPerRow = mipSize[0] / blockWidth;
+ const blocksPerColumn = mipSize[1] / blockHeight;
+
+ assert(blocksPerRow % 1 === 0);
+ assert(blocksPerColumn % 1 === 0);
+
+ const bytesPerRow = align(blocksPerRow * bytesPerBlock, 256);
+ const byteLength = bytesPerRow * blocksPerColumn * mipSize[2];
+
+ return this.copyWholeTextureToNewBuffer(
+ { texture, mipLevel },
+ {
+ bytesPerBlock,
+ bytesPerRow,
+ rowsPerImage: blocksPerColumn,
+ byteLength
+ }
+ );
+ }
+
+ copyWholeTextureToNewBuffer(
+ { texture, mipLevel },
+ resultDataLayout)
+
+
+
+
+
+ {
+ const { byteLength, bytesPerRow, rowsPerImage } = resultDataLayout;
+ const buffer = this.device.createBuffer({
+ size: align(byteLength, 4), // this is necessary because we need to copy and map data from this buffer
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ });
+ this.trackForCleanup(buffer);
+
+ const mipSize = physicalMipSizeFromTexture(texture, mipLevel || 0);
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel },
+ { buffer, bytesPerRow, rowsPerImage },
+ mipSize
+ );
+ this.device.queue.submit([encoder.finish()]);
+
+ return buffer;
+ }
+
+ updateLinearTextureDataSubBox(
+ format,
+ copySize,
+ copyParams)
+
+
+
+ {
+ const { src, dest } = copyParams;
+ const rowLength = bytesInACompleteRow(copySize.width, format);
+ for (const texel of this.iterateBlockRows(copySize, format)) {
+ const srcOffsetElements = this.getTexelOffsetInBytes(
+ src.dataLayout,
+ format,
+ texel,
+ src.origin
+ );
+ const dstOffsetElements = this.getTexelOffsetInBytes(
+ dest.dataLayout,
+ format,
+ texel,
+ dest.origin
+ );
+ memcpy(
+ { src: src.data, start: srcOffsetElements, length: rowLength },
+ { dst: dest.data, start: dstOffsetElements }
+ );
+ }
+ }
+
+ /** Offset for a particular texel in the linear texture data */
+ getTexelOffsetInBytes(
+ textureDataLayout,
+ format,
+ texel,
+ origin = { x: 0, y: 0, z: 0 })
+ {
+ const { offset, bytesPerRow, rowsPerImage } = textureDataLayout;
+ const info = kTextureFormatInfo[format];
+
+ assert(texel.x % info.blockWidth === 0);
+ assert(texel.y % info.blockHeight === 0);
+ assert(origin.x % info.blockWidth === 0);
+ assert(origin.y % info.blockHeight === 0);
+
+ const bytesPerImage = rowsPerImage * bytesPerRow;
+
+ return (
+ offset +
+ (texel.z + origin.z) * bytesPerImage +
+ (texel.y + origin.y) / info.blockHeight * bytesPerRow +
+ (texel.x + origin.x) / info.blockWidth * info.color.bytes);
+
+ }
+
+ *iterateBlockRows(
+ size,
+ format)
+ {
+ if (size.width === 0 || size.height === 0 || size.depthOrArrayLayers === 0) {
+ // do not iterate anything for an empty region
+ return;
+ }
+ const info = kTextureFormatInfo[format];
+ assert(size.height % info.blockHeight === 0);
+ // Note: it's important that the order is in increasing memory address order.
+ for (let z = 0; z < size.depthOrArrayLayers; ++z) {
+ for (let y = 0; y < size.height; y += info.blockHeight) {
+ yield {
+ x: 0,
+ y,
+ z
+ };
+ }
+ }
+ }
+ }
+
+ return TextureExpectations;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/constants/flags.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/constants/flags.spec.js
new file mode 100644
index 0000000000..f7005eea26
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/constants/flags.spec.js
@@ -0,0 +1,79 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test the values of flags interfaces (e.g. GPUTextureUsage).
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { IDLTest } from '../idl_test.js';
+
+export const g = makeTestGroup(IDLTest);
+
+const kBufferUsageExp = {
+ MAP_READ: 0x0001,
+ MAP_WRITE: 0x0002,
+ COPY_SRC: 0x0004,
+ COPY_DST: 0x0008,
+ INDEX: 0x0010,
+ VERTEX: 0x0020,
+ UNIFORM: 0x0040,
+ STORAGE: 0x0080,
+ INDIRECT: 0x0100,
+ QUERY_RESOLVE: 0x0200
+};
+g.test('BufferUsage,count').fn((t) => {
+ t.assertMemberCount(GPUBufferUsage, kBufferUsageExp);
+});
+g.test('BufferUsage,values').
+params((u) => u.combine('key', Object.keys(kBufferUsageExp))).
+fn((t) => {
+ const { key } = t.params;
+ t.assertMember(GPUBufferUsage, kBufferUsageExp, key);
+});
+
+const kTextureUsageExp = {
+ COPY_SRC: 0x01,
+ COPY_DST: 0x02,
+ TEXTURE_BINDING: 0x04,
+ STORAGE_BINDING: 0x08,
+ RENDER_ATTACHMENT: 0x10
+};
+g.test('TextureUsage,count').fn((t) => {
+ t.assertMemberCount(GPUTextureUsage, kTextureUsageExp);
+});
+g.test('TextureUsage,values').
+params((u) => u.combine('key', Object.keys(kTextureUsageExp))).
+fn((t) => {
+ const { key } = t.params;
+ t.assertMember(GPUTextureUsage, kTextureUsageExp, key);
+});
+
+const kColorWriteExp = {
+ RED: 0x1,
+ GREEN: 0x2,
+ BLUE: 0x4,
+ ALPHA: 0x8,
+ ALL: 0xf
+};
+g.test('ColorWrite,count').fn((t) => {
+ t.assertMemberCount(GPUColorWrite, kColorWriteExp);
+});
+g.test('ColorWrite,values').
+params((u) => u.combine('key', Object.keys(kColorWriteExp))).
+fn((t) => {
+ const { key } = t.params;
+ t.assertMember(GPUColorWrite, kColorWriteExp, key);
+});
+
+const kShaderStageExp = {
+ VERTEX: 0x1,
+ FRAGMENT: 0x2,
+ COMPUTE: 0x4
+};
+g.test('ShaderStage,count').fn((t) => {
+ t.assertMemberCount(GPUShaderStage, kShaderStageExp);
+});
+g.test('ShaderStage,values').
+params((u) => u.combine('key', Object.keys(kShaderStageExp))).
+fn((t) => {
+ const { key } = t.params;
+ t.assertMember(GPUShaderStage, kShaderStageExp, key);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.html.js
new file mode 100644
index 0000000000..a62aeff863
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.html.js
@@ -0,0 +1,52 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // WPT-specific test checking that WebGPU is available iff isSecureContext.
+import { assert } from '../../common/util/util.js';
+const items = [
+globalThis.navigator.gpu,
+globalThis.GPU,
+globalThis.GPUAdapter,
+globalThis.GPUAdapterInfo,
+globalThis.GPUBindGroup,
+globalThis.GPUBindGroupLayout,
+globalThis.GPUBuffer,
+globalThis.GPUBufferUsage,
+globalThis.GPUCanvasContext,
+globalThis.GPUColorWrite,
+globalThis.GPUCommandBuffer,
+globalThis.GPUCommandEncoder,
+globalThis.GPUCompilationInfo,
+globalThis.GPUCompilationMessage,
+globalThis.GPUComputePassEncoder,
+globalThis.GPUComputePipeline,
+globalThis.GPUDevice,
+globalThis.GPUDeviceLostInfo,
+globalThis.GPUError,
+globalThis.GPUExternalTexture,
+globalThis.GPUMapMode,
+globalThis.GPUOutOfMemoryError,
+globalThis.GPUPipelineLayout,
+globalThis.GPUQuerySet,
+globalThis.GPUQueue,
+globalThis.GPURenderBundle,
+globalThis.GPURenderBundleEncoder,
+globalThis.GPURenderPassEncoder,
+globalThis.GPURenderPipeline,
+globalThis.GPUSampler,
+globalThis.GPUShaderModule,
+globalThis.GPUShaderStage,
+globalThis.GPUSupportedLimits,
+globalThis.GPUTexture,
+globalThis.GPUTextureUsage,
+globalThis.GPUTextureView,
+globalThis.GPUUncapturedErrorEvent,
+globalThis.GPUValidationError];
+
+
+for (const item of items) {
+ if (globalThis.isSecureContext) {
+ assert(item !== undefined, 'Item/interface should be exposed on secure context');
+ } else {
+ assert(item === undefined, 'Item/interface should not be exposed on insecure context');
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.http.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.http.html
new file mode 100644
index 0000000000..94a814d005
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.http.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>WebGPU exposed items (non-HTTPS)</title>
+ <meta name=assert content="WebGPU should not be exposed on a non-[SecureContext]">
+ <link rel=help href='https://gpuweb.github.io/gpuweb/'>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script type=module src=exposed.html.js></script>
+ </head>
+ <body></body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.https.html
new file mode 100644
index 0000000000..8d421b7020
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/exposed.https.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>WebGPU exposed items (HTTPS)</title>
+ <meta name=assert content="All specified WebGPU items/interfaces should be exposed, on a [SecureContext]">
+ <link rel=help href='https://gpuweb.github.io/gpuweb/'>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script type=module src=exposed.html.js></script>
+ </head>
+ <body></body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/idl_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/idl_test.js
new file mode 100644
index 0000000000..147266da65
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/idl/idl_test.js
@@ -0,0 +1,41 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { Fixture } from '../../common/framework/fixture.js';import { getGPU } from '../../common/util/navigator_gpu.js';import { assert } from '../../common/util/util.js';
+
+
+
+
+
+/**
+ * Base fixture for testing the exposed interface is correct (without actually using WebGPU).
+ */
+export class IDLTest extends Fixture {
+ init() {
+ // Ensure the GPU provider is initialized
+ getGPU(this.rec);
+ return Promise.resolve();
+ }
+
+ /**
+ * Asserts that a member of an IDL interface has the expected value.
+ */
+ assertMember(act, exp, key) {
+ assert(key in act, () => `Expected key ${key} missing`);
+ assert(act[key] === exp[key], () => `Value of [${key}] was ${act[key]}, expected ${exp[key]}`);
+ }
+
+ /**
+ * Asserts that an IDL interface has the same number of keys as the
+ *
+ * MAINTENANCE_TODO: add a way to check for the types of keys with unknown values, like methods and attributes
+ * MAINTENANCE_TODO: handle extensions
+ */
+ assertMemberCount(act, exp) {
+ const expKeys = Object.keys(exp);
+ const actKeys = Object.keys(act);
+ assert(
+ actKeys.length === expKeys.length,
+ () => `Had ${actKeys.length} keys, expected ${expKeys.length}`
+ );
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/listing.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/listing.js
new file mode 100644
index 0000000000..7632956de5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/listing.js
@@ -0,0 +1,4223 @@
+// AUTO-GENERATED - DO NOT EDIT. See src/common/tools/gen_listings.ts.
+
+export const listing = [
+ {
+ "file": [],
+ "readme": "WebGPU conformance test suite."
+ },
+ {
+ "file": [
+ "api"
+ ],
+ "readme": "Tests for full coverage of the Javascript API surface of WebGPU."
+ },
+ {
+ "file": [
+ "api",
+ "operation"
+ ],
+ "readme": "Tests that check the result of performing valid WebGPU operations, taking advantage of\nparameterization to exercise interactions between features."
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "adapter",
+ "requestAdapter"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "adapter",
+ "requestAdapterInfo"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "adapter",
+ "requestDevice"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "async_ordering"
+ ],
+ "readme": "Test ordering of async resolutions between promises returned by the following calls (and possibly\nbetween multiple of the same call), where there are constraints on the ordering.\nSpec issue: https://github.com/gpuweb/gpuweb/issues/962\n\nTODO: plan and implement\n- createReadyPipeline() (not sure if this actually has any ordering constraints)\n- cmdbuf.executionTime\n- device.popErrorScope()\n- device.lost\n- queue.onSubmittedWorkDone()\n- buffer.mapAsync()\n- shadermodule.getCompilationInfo()"
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "buffers"
+ ],
+ "readme": "GPUBuffer tests."
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "buffers",
+ "map"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "buffers",
+ "map_ArrayBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "buffers",
+ "map_detach"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "buffers",
+ "map_oom"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "buffers",
+ "threading"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "basic"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "clearBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "copyBufferToBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "copyTextureToTexture"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "image_copy"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "programmable",
+ "state_tracking"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "queries"
+ ],
+ "readme": "TODO: test the behavior of creating/using/resolving queries.\n- timestamp\n- nested (e.g. timestamp inside occlusion query), if any such cases are valid. Try\n writing to the same query set (at same or different indices), if valid. Check results make sense.\n- start a query (all types) with no draw calls"
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "queries",
+ "occlusionQuery"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "render",
+ "dynamic_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "command_buffer",
+ "render",
+ "state_tracking"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "compute",
+ "basic"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "compute_pipeline",
+ "entry_point_name"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "compute_pipeline",
+ "overrides"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "device",
+ "lost"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "labels"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "memory_allocation"
+ ],
+ "readme": "Try to stress memory allocators in the implementation and driver.\n\nTODO: plan and implement\n- Tests which (pseudo-randomly?) allocate a bunch of memory and then assert things about the memory\n (it's not aliased, it's valid to read and write in various ways, accesses read/write the correct data)\n - Possibly also with OOB accesses/robust buffer access?\n- Tests which are targeted against particular known implementation details"
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "memory_sync",
+ "buffer",
+ "multiple_buffers"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "memory_sync",
+ "buffer",
+ "single_buffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "memory_sync",
+ "texture",
+ "same_subresource"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "onSubmittedWorkDone"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "pipeline",
+ "default_layout"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "queue",
+ "writeBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "reflection"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pass"
+ ],
+ "readme": "Render pass stuff other than commands (which are in command_buffer/)."
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pass",
+ "clear_value"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pass",
+ "resolve"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pass",
+ "storeOp"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pass",
+ "storeop2"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pipeline",
+ "culling_tests"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pipeline",
+ "overrides"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pipeline",
+ "pipeline_output_targets"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pipeline",
+ "primitive_topology"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pipeline",
+ "sample_mask"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "render_pipeline",
+ "vertex_only_render_pipeline"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "basic"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "color_target_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "depth"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "depth_bias"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "depth_clip_clamp"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "draw"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "indirect_draw"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "robust_access_index"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "rendering",
+ "stencil"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "resource_init",
+ "buffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "resource_init",
+ "texture_zero"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "sampling",
+ "anisotropy"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "sampling",
+ "filter_mode"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "sampling",
+ "lod_clamp"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "shader_module",
+ "compilation_info"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "texture_view",
+ "format_reinterpretation"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "texture_view",
+ "read"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "texture_view",
+ "write"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "threading"
+ ],
+ "readme": "Tests for behavior with multiple threads (main thread + workers).\n\nTODO: plan and implement\n- 'postMessage'\n Try postMessage'ing an object of every type (to same or different thread)\n - {main -> main, main -> worker, worker -> main, worker1 -> worker1, worker1 -> worker2}\n - through {global postMessage, MessageChannel}\n - {in, not in} transferrable object list, when valid\n- 'concurrency'\n Short tight loop doing many of an action from two threads at the same time\n - e.g. {create {buffer, texture, shader, pipeline}}"
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "uncapturederror"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "vertex_state",
+ "correctness"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "operation",
+ "vertex_state",
+ "index_format"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "regression"
+ ],
+ "readme": "One-off tests that reproduce API bugs found in implementations to prevent the bugs from\nappearing again."
+ },
+ {
+ "file": [
+ "api",
+ "validation"
+ ],
+ "readme": "Positive and negative tests for all the validation rules of the API."
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "buffer",
+ "create"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "buffer",
+ "destroy"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "buffer",
+ "mapping"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "buffer",
+ "threading"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "features"
+ ],
+ "readme": "Test every method or option that shouldn't be allowed without a feature enabled.\nIf the feature is not enabled, any use of an enum value added by a feature must be an\n*exception*, per <https://github.com/gpuweb/gpuweb/blob/main/design/ErrorConventions.md>.\n\n- x= that feature {enabled, disabled}\n\nGenerally one file for each feature name, but some may be grouped (e.g. one file for all optional\nquery types, one file for all optional texture formats).\n\nTODO: implement"
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "features",
+ "query_types"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "features",
+ "texture_formats"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits"
+ ],
+ "readme": "Test everything that shouldn't be valid without a higher-than-specified limit.\n\n- x= that limit {default, max supported (if different), lower than default (TODO: if allowed)}\n\nOne file for each limit name.\n\nTODO: implement\nTODO: Also test that \"alignment\" limits require a power of 2."
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxBindGroups"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxBindingsPerBindGroup"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxBufferSize"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxColorAttachmentBytesPerSample"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxColorAttachments"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxComputeInvocationsPerWorkgroup"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxComputeWorkgroupSizeX"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxComputeWorkgroupSizeY"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxComputeWorkgroupSizeZ"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxComputeWorkgroupStorageSize"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxComputeWorkgroupsPerDimension"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxDynamicStorageBuffersPerPipelineLayout"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxDynamicUniformBuffersPerPipelineLayout"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxInterStageShaderComponents"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxInterStageShaderVariables"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxSampledTexturesPerShaderStage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxSamplersPerShaderStage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxStorageBufferBindingSize"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxStorageBuffersPerShaderStage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxStorageTexturesPerShaderStage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxTextureArrayLayers"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxTextureDimension1D"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxTextureDimension2D"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxTextureDimension3D"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxUniformBufferBindingSize"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxUniformBuffersPerShaderStage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxVertexAttributes"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxVertexBufferArrayStride"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "maxVertexBuffers"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "minStorageBufferOffsetAlignment"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "capability_checks",
+ "limits",
+ "minUniformBufferOffsetAlignment"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "compute_pipeline"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "createBindGroup"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "createBindGroupLayout"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "createPipelineLayout"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "createSampler"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "createTexture"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "createView"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "debugMarker"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "beginComputePass"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "beginRenderPass"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "clearBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "compute_pass"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "copyBufferToBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "copyTextureToTexture"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "debug"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "index_access"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "draw"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "dynamic_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "indirect_draw"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "setIndexBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "setPipeline"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "setVertexBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render",
+ "state_tracking"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "render_pass"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "setBindGroup"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "createRenderBundleEncoder"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "encoder_open_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "encoder_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "programmable",
+ "pipeline_bind_group_compat"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "queries",
+ "begin_end"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "queries",
+ "general"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "queries",
+ "resolveQuerySet"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "encoding",
+ "render_bundle"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "error_scope"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "getBindGroupLayout"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "gpu_external_texture_expiration"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "image_copy"
+ ],
+ "readme": "writeTexture + copyBufferToTexture + copyTextureToBuffer validation tests.\n\nTest coverage:\n* resource usages:\n - texture_usage_must_be_valid: for GPUTextureUsage::COPY_SRC, GPUTextureUsage::COPY_DST flags.\n - buffer_usage_must_be_valid: for GPUBufferUsage::COPY_SRC, GPUBufferUsage::COPY_DST flags.\n\n* textureCopyView:\n - texture_must_be_valid: for valid, destroyed, error textures.\n - sample_count_must_be_1: for sample count 1 and 4.\n - mip_level_must_be_in_range: for various combinations of mipLevel and mipLevelCount.\n - format: for all formats with full and non-full copies on width, height, and depth.\n - texel_block_alignment_on_origin: for all formats and coordinates.\n\n* bufferCopyView:\n - buffer_must_be_valid: for valid, destroyed, error buffers.\n - bytes_per_row_alignment: for bytesPerRow to be 256-byte aligned or not, and bytesPerRow is required or not.\n\n* linear texture data:\n - bound_on_rows_per_image: for various combinations of copyDepth (1, >1), copyHeight, rowsPerImage.\n - offset_plus_required_bytes_in_copy_overflow\n - required_bytes_in_copy: testing minimal data size and data size too small for various combinations of bytesPerRow, rowsPerImage, copyExtent and offset. for the copy method, bytesPerRow is computed as bytesInACompleteRow aligned to be a multiple of 256 + bytesPerRowPadding * 256.\n - texel_block_alignment_on_rows_per_image: for all formats.\n - offset_alignment: for all formats.\n - bound_on_offset: for various combinations of offset and dataSize.\n\n* texture copy range:\n - 1d_texture: copyExtent.height isn't 1, copyExtent.depthOrArrayLayers isn't 1.\n - texel_block_alignment_on_size: for all formats and coordinates.\n - texture_range_conditons: for all coordinate and various combinations of origin, copyExtent, textureSize and mipLevel.\n\nTODO: more test coverage for 1D and 3D textures."
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "image_copy",
+ "buffer_related"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "image_copy",
+ "buffer_texture_copies"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "image_copy",
+ "layout_related"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "image_copy",
+ "texture_related"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "layout_shader_compat"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "query_set",
+ "create"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "query_set",
+ "destroy"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue"
+ ],
+ "readme": "Tests for validation that occurs inside queued operations\n(submit, writeBuffer, writeTexture, copyExternalImageToTexture).\n\nBufferMapStatesToTest = {\n mapped -> unmapped,\n mapped at creation -> unmapped,\n mapping pending -> unmapped,\n pending -> mapped (await map),\n unmapped -> pending (noawait map),\n created mapped-at-creation,\n}\n\nNote writeTexture is tested in image_copy."
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "buffer_mapped"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "copyToTexture",
+ "CopyExternalImageToTexture"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "destroyed",
+ "buffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "destroyed",
+ "query_set"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "destroyed",
+ "texture"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "submit"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "writeBuffer"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "queue",
+ "writeTexture"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pass"
+ ],
+ "readme": "Render pass stuff other than commands (which are in encoding/cmds/)."
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pass",
+ "attachment_compatibility"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pass",
+ "render_pass_descriptor"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pass",
+ "resolve"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "depth_stencil_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "fragment_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "inter_stage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "misc"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "multisample_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "overrides"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "primitive_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "shader_module"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "render_pipeline",
+ "vertex_state"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "resource_usages",
+ "buffer"
+ ],
+ "readme": "TODO: look at texture,*"
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "resource_usages",
+ "buffer",
+ "in_pass_encoder"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "resource_usages",
+ "buffer",
+ "in_pass_misc"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "resource_usages",
+ "texture",
+ "in_pass_encoder"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "resource_usages",
+ "texture",
+ "in_render_common"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "resource_usages",
+ "texture",
+ "in_render_misc"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "shader_module",
+ "entry_point"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "shader_module",
+ "overrides"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "state",
+ "device_lost"
+ ],
+ "readme": "Tests of behavior while the device is lost.\n\n- x= every method in the API.\n\nTODO: implement"
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "state",
+ "device_lost",
+ "destroy"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "texture",
+ "bgra8unorm_storage"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "texture",
+ "destroy"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "texture",
+ "float32_filterable"
+ ]
+ },
+ {
+ "file": [
+ "api",
+ "validation",
+ "texture",
+ "rg11b10ufloat_renderable"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "encoding",
+ "cmds",
+ "copyTextureToBuffer"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "encoding",
+ "programmable",
+ "pipeline_bind_group_compat"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "render_pipeline",
+ "fragment_state"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "render_pipeline",
+ "shader_module"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "render_pipeline",
+ "vertex_state"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "texture",
+ "createTexture"
+ ]
+ },
+ {
+ "file": [
+ "compat",
+ "api",
+ "validation",
+ "texture",
+ "cubeArray"
+ ]
+ },
+ {
+ "file": [
+ "examples"
+ ]
+ },
+ {
+ "file": [
+ "idl"
+ ],
+ "readme": "Tests to check that the WebGPU IDL is correctly implemented, for examples that objects exposed\nexactly the correct members, and that methods throw when passed incomplete dictionaries.\n\nSee https://github.com/gpuweb/cts/issues/332\n\nTODO: exposed.html.ts: Test all WebGPU interfaces instead of just some of them.\nTODO: Check prototype chains. (Add a helper in IDLTest for this.)"
+ },
+ {
+ "file": [
+ "idl",
+ "constants",
+ "flags"
+ ]
+ },
+ {
+ "file": [
+ "shader"
+ ],
+ "readme": "Tests for full coverage of the shaders that can be passed to WebGPU."
+ },
+ {
+ "file": [
+ "shader",
+ "execution"
+ ],
+ "readme": "Tests that check the result of valid shader execution."
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_addition"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_comparison"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_division"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_matrix_addition"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_matrix_subtraction"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_remainder"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "af_subtraction"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "bitwise"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "bitwise_shift"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "bool_logical"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_addition"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_comparison"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_division"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_matrix_addition"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_matrix_matrix_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_matrix_scalar_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_matrix_subtraction"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_matrix_vector_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_remainder"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f16_subtraction"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_addition"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_comparison"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_division"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_matrix_addition"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_matrix_matrix_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_matrix_scalar_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_matrix_subtraction"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_matrix_vector_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_multiplication"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_remainder"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "f32_subtraction"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "i32_arithmetic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "i32_comparison"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "u32_arithmetic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "binary",
+ "u32_comparison"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "abs"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "acos"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "acosh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "all"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "any"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "arrayLength"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "asin"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "asinh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atan"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atan2"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atanh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicAdd"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicAnd"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicCompareExchangeWeak"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicExchange"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicLoad"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicMax"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicMin"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicOr"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicStore"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicSub"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "atomics",
+ "atomicXor"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "bitcast"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "ceil"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "clamp"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "cos"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "cosh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "countLeadingZeros"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "countOneBits"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "countTrailingZeros"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "cross"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "degrees"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "determinant"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "distance"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dot"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dpdx"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dpdxCoarse"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dpdxFine"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dpdy"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dpdyCoarse"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "dpdyFine"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "exp"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "exp2"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "extractBits"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "faceForward"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "firstLeadingBit"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "firstTrailingBit"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "floor"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "fma"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "fract"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "frexp"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "fwidth"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "fwidthCoarse"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "fwidthFine"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "insertBits"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "inversesqrt"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "ldexp"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "length"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "log"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "log2"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "max"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "min"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "mix"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "modf"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "normalize"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "pack2x16float"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "pack2x16snorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "pack2x16unorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "pack4x8snorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "pack4x8unorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "pow"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "quantizeToF16"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "radians"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "reflect"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "refract"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "reverseBits"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "round"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "saturate"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "select"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "sign"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "sin"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "sinh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "smoothstep"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "sqrt"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "step"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "storageBarrier"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "tan"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "tanh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureDimension"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureGather"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureGatherCompare"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureLoad"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureNumLayers"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureNumLevels"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureNumSamples"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureSample"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureSampleBias"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureSampleCompare"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureSampleCompareLevel"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureSampleGrad"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureSampleLevel"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "textureStore"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "transpose"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "trunc"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "unpack2x16float"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "unpack2x16snorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "unpack2x16unorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "unpack4x8snorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "unpack4x8unorm"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "call",
+ "builtin",
+ "workgroupBarrier"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "af_arithmetic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "af_assignment"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "bool_conversion"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "bool_logical"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "f16_arithmetic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "f16_conversion"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "f32_arithmetic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "f32_conversion"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "i32_arithmetic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "i32_complement"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "i32_conversion"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "u32_complement"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "expression",
+ "unary",
+ "u32_conversion"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "float_parse"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "call"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "complex"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "eval_order"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "for"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "if"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "loop"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "phony"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "return"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "switch"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "flow_control",
+ "while"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "memory_model",
+ "adjacent"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "memory_model",
+ "atomicity"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "memory_model",
+ "barrier"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "memory_model",
+ "coherence"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "memory_model",
+ "weak"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "padding"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "robust_access"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "robust_access_vertex"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "shader_io",
+ "compute_builtins"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "shader_io",
+ "shared_structs"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "shadow"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "statement",
+ "increment_decrement"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "execution",
+ "zero_init"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "regression"
+ ],
+ "readme": "One-off tests that reproduce shader bugs found in implementations to prevent the bugs from\nappearing again."
+ },
+ {
+ "file": [
+ "shader",
+ "validation"
+ ],
+ "readme": "Positive and negative tests for all the validation rules of the shading language."
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "const_assert",
+ "const_assert"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "decl",
+ "const"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "decl",
+ "override"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "decl",
+ "ptr_spelling"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "decl",
+ "var_access_mode"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "access",
+ "vector"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "binary",
+ "bitwise_shift"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "abs"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "acos"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "acosh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "asin"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "asinh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "atan"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "atan2"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "atanh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "atomics"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "bitcast"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "ceil"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "clamp"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "cos"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "cosh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "degrees"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "exp"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "exp2"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "inverseSqrt"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "length"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "log"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "log2"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "modf"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "radians"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "round"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "saturate"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "sign"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "sin"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "sinh"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "sqrt"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "expression",
+ "call",
+ "builtin",
+ "tan"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "functions",
+ "alias_analysis"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "functions",
+ "restrictions"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "align"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "attribute"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "binary_ops"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "blankspace"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "break"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "builtin"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "comments"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "const"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "const_assert"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "diagnostic"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "discard"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "enable"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "identifiers"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "literal"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "must_use"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "pipeline_stage"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "semicolon"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "source"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "unary_ops"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "parse",
+ "var_and_let"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "binding"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "builtins"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "entry_point"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "group"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "group_and_binding"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "id"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "interpolate"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "invariant"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "locations"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "size"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "shader_io",
+ "workgroup_size"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "types",
+ "alias"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "types",
+ "struct"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "types",
+ "vector"
+ ]
+ },
+ {
+ "file": [
+ "shader",
+ "validation",
+ "uniformity",
+ "uniformity"
+ ]
+ },
+ {
+ "file": [
+ "util",
+ "texture",
+ "texel_data"
+ ]
+ },
+ {
+ "file": [
+ "util",
+ "texture",
+ "texture_ok"
+ ]
+ },
+ {
+ "file": [
+ "web_platform"
+ ],
+ "readme": "Tests for Web platform-specific interactions like GPUCanvasContext and canvas, WebXR,\nImageBitmaps, and video APIs.\n\nTODO(#922): Also hopefully tests for user-initiated readbacks from WebGPU canvases\n(printing, save image as, etc.)"
+ },
+ {
+ "file": [
+ "web_platform",
+ "canvas"
+ ],
+ "readme": "Tests for WebGPU <canvas> and OffscreenCanvas presentation."
+ },
+ {
+ "file": [
+ "web_platform",
+ "canvas",
+ "configure"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "canvas",
+ "context_creation"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "canvas",
+ "getCurrentTexture"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "canvas",
+ "getPreferredCanvasFormat"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "canvas",
+ "readbackFromWebGPUCanvas"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "copyToTexture",
+ "ImageBitmap"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "copyToTexture",
+ "ImageData"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "copyToTexture"
+ ],
+ "readme": "Tests for copyToTexture from all possible sources (video, canvas, ImageBitmap, ...)"
+ },
+ {
+ "file": [
+ "web_platform",
+ "copyToTexture",
+ "canvas"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "copyToTexture",
+ "image"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "copyToTexture",
+ "video"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "external_texture"
+ ],
+ "readme": "Tests for external textures."
+ },
+ {
+ "file": [
+ "web_platform",
+ "external_texture",
+ "video"
+ ]
+ },
+ {
+ "file": [
+ "web_platform",
+ "reftests"
+ ],
+ "readme": "Reference tests (reftests) for WebGPU canvas presentation.\n\nThese render some contents to a canvas using WebGPU, and WPT compares the rendering result with\nthe \"reference\" versions (in `ref/`) which render with 2D canvas.\n\nThis tests things like:\n- The canvas has the correct orientation.\n- The canvas renders with the correct transfer function.\n- The canvas blends and interpolates in the correct color encoding.\n\nTODO(#918): Test all possible color spaces (once we have more than 1)\nTODO(#921): Why is there sometimes a difference of 1 (e.g. 3f vs 40) in canvas_size_different_with_back_buffer_size?\nAnd why does chromium's image_diff show diffs on other pixels that don't seem to have diffs?\nTODO(#1093): Test rgba16float values which are out of gamut of the canvas but under SDR luminance.\nTODO(#1093): Test rgba16float values which are above SDR luminance.\nTODO(#1116): Test canvas scaling.\nTODO: Test transferControlToOffscreen, used from {the same,another} thread"
+ },
+ {
+ "file": [
+ "web_platform",
+ "worker",
+ "worker"
+ ]
+ }
+];
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_addition.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_addition.spec.js
new file mode 100644
index 0000000000..c03c90b773
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_addition.spec.js
@@ -0,0 +1,154 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for non-matrix AbstractFloat addition expression
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+const additionVectorScalarInterval = (v, s) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s, v) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.additionInterval(s, e)));
+};
+
+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].
+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].
+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(
+ `
+Expression: x + y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('scalar');
+ await run(
+ t,
+ abstractBinary('+'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
+ await run(
+ t,
+ abstractBinary('+'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`vec${dim}_scalar`);
+ await run(
+ t,
+ abstractBinary('+'),
+ [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`scalar_vec${dim}`);
+ await run(
+ t,
+ abstractBinary('+'),
+ [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_comparison.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_comparison.spec.js
new file mode 100644
index 0000000000..3d80b3ec6d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_comparison.spec.js
@@ -0,0 +1,214 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for the AbstractFloat 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,
+
+ TypeAbstractFloat,
+ TypeBool } from
+'../../../../util/conversion.js';
+import { flushSubnormalNumberF64, vectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, 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 truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+lhs,
+rhs,
+truthFunc)
+{
+ // 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 = [];
+ 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, rhs) => {
+ return lhs.value === rhs.value;
+ };
+
+ return vectorF64Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value !== rhs.value;
+ };
+
+ return vectorF64Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value < rhs.value;
+ };
+
+ return vectorF64Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value <= rhs.value;
+ };
+
+ return vectorF64Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value > rhs.value;
+ };
+
+ return vectorF64Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value >= rhs.value;
+ };
+
+ 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(
+ `
+Expression: x == y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.
+combine('inputSource', [allInputSources[0]] /* const */).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('equals');
+ await run(t, binary('=='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+});
+
+g.test('not_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x != y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.
+combine('inputSource', [allInputSources[0]] /* const */).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('not_equals');
+ await run(t, binary('!='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+});
+
+g.test('less_than').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x < y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.
+combine('inputSource', [allInputSources[0]] /* const */).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('less_than');
+ await run(t, binary('<'), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+});
+
+g.test('less_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x <= y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.
+combine('inputSource', [allInputSources[0]] /* const */).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('less_equals');
+ await run(t, binary('<='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+});
+
+g.test('greater_than').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x > y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.
+combine('inputSource', [allInputSources[0]] /* const */).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('greater_than');
+ await run(t, binary('>'), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+});
+
+g.test('greater_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x >= y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.
+combine('inputSource', [allInputSources[0]] /* const */).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('greater_equals');
+ await run(t, binary('>='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_division.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_division.spec.js
new file mode 100644
index 0000000000..34d5d7b836
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_division.spec.js
@@ -0,0 +1,154 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for non-matrix AbstractFloat 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 } from '../../../../util/floating_point.js';
+import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+const divisionVectorScalarInterval = (v, s) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s, v) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.divisionInterval(s, e)));
+};
+
+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].
+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].
+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(
+ `
+Expression: x / y, where x and y are scalars
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('scalar');
+ await run(
+ t,
+ abstractBinary('/'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x and y are vectors
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) =>
+u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
+ await run(
+ t,
+ abstractBinary('/'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`vec${dim}_scalar`);
+ await run(
+ t,
+ abstractBinary('/'),
+ [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`scalar_vec${dim}`);
+ await run(
+ t,
+ abstractBinary('/'),
+ [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.js
new file mode 100644
index 0000000000..6fc50ca540
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.js
@@ -0,0 +1,61 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for matrix AbstractFloat addition expressions
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeMat } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR
+const mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].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(
+ `
+Expression: x + y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractBinary('+'),
+ [TypeMat(cols, rows, TypeAbstractFloat), TypeMat(cols, rows, TypeAbstractFloat)],
+ TypeMat(cols, rows, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.js
new file mode 100644
index 0000000000..09dbcdf692
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.js
@@ -0,0 +1,61 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for matrix AbstractFloat subtraction expression
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeMat } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR
+const mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].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(
+ `
+Expression: x - y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractBinary('-'),
+ [TypeMat(cols, rows, TypeAbstractFloat), TypeMat(cols, rows, TypeAbstractFloat)],
+ TypeMat(cols, rows, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_multiplication.spec.js
new file mode 100644
index 0000000000..6f8cc65f4e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_multiplication.spec.js
@@ -0,0 +1,154 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for non-matrix AbstractFloat multiplication expression
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+const multiplicationVectorScalarInterval = (v, s) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s, v) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.multiplicationInterval(s, e)));
+};
+
+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].
+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].
+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(
+ `
+Expression: x * y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('scalar');
+ await run(
+ t,
+ abstractBinary('*'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
+ await run(
+ t,
+ abstractBinary('*'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`vec${dim}_scalar`);
+ await run(
+ t,
+ abstractBinary('*'),
+ [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`scalar_vec${dim}`);
+ await run(
+ t,
+ abstractBinary('*'),
+ [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_remainder.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_remainder.spec.js
new file mode 100644
index 0000000000..cbb45f45c6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_remainder.spec.js
@@ -0,0 +1,154 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+const remainderVectorScalarInterval = (v, s) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s, v) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.remainderInterval(s, e)));
+};
+
+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].
+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].
+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(
+ `
+Expression: x % y, where x and y are scalars
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('scalar');
+ await run(
+ t,
+ abstractBinary('%'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x and y are vectors
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) =>
+u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
+ await run(
+ t,
+ abstractBinary('%'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`vec${dim}_scalar`);
+ await run(
+ t,
+ abstractBinary('%'),
+ [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`scalar_vec${dim}`);
+ await run(
+ t,
+ abstractBinary('%'),
+ [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_subtraction.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_subtraction.spec.js
new file mode 100644
index 0000000000..0782cf61be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/af_subtraction.spec.js
@@ -0,0 +1,154 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for non-matrix AbstractFloat subtraction expression
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeVec } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { sparseF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractBinary } from './binary.js';
+
+const subtractionVectorScalarInterval = (v, s) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s, v) => {
+ return FP.abstract.toVector(v.map((e) => FP.abstract.subtractionInterval(s, e)));
+};
+
+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].
+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].
+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(
+ `
+Expression: x - y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('scalar');
+ await run(
+ t,
+ abstractBinary('-'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', onlyConstInputSource).combine('vectorize', [2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
+ await run(
+ t,
+ abstractBinary('-'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`vec${dim}_scalar`);
+ await run(
+ t,
+ abstractBinary('-'),
+ [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(`scalar_vec${dim}`);
+ await run(
+ t,
+ abstractBinary('-'),
+ [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
+ TypeVec(dim, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/binary.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/binary.js
new file mode 100644
index 0000000000..f3f84db87c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/binary.js
@@ -0,0 +1,21 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { basicExpressionBuilder,
+compoundAssignmentBuilder,
+abstractFloatShaderBuilder } from
+'../expression.js';
+
+/* @returns a ShaderBuilder that evaluates a binary operation */
+export function binary(op) {
+ return basicExpressionBuilder((values) => `(${values.map((v) => `(${v})`).join(op)})`);
+}
+
+/* @returns a ShaderBuilder that evaluates a compound binary operation */
+export function compoundBinary(op) {
+ return compoundAssignmentBuilder(op);
+}
+
+/* @returns a ShaderBuilder that evaluates a binary operation that returns AbstractFloats */
+export function abstractBinary(op) {
+ return abstractFloatShaderBuilder((values) => `(${values.map((v) => `(${v})`).join(op)})`);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise.spec.js
new file mode 100644
index 0000000000..c442f5cfa4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise.spec.js
@@ -0,0 +1,303 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for the bitwise binary expression operations
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { i32, scalarType, u32 } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function makeBitwiseOrCases(inputType) {
+ const V = inputType === 'i32' ? i32 : u32;
+ const cases = [
+ // Static patterns
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
+ expected: V(0b11111111111111111111111111111111)
+ },
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
+ expected: V(0b11111111111111111111111111111111)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
+ expected: V(0b11111111111111111111111111111111)
+ },
+ {
+ input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
+ expected: V(0b10100100010010100100010010100100)
+ },
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
+ expected: V(0b10100100010010100100010010100100)
+ },
+ {
+ input: [V(0b01010010001001010010001001010010), V(0b10100100010010100100010010100100)],
+ expected: V(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)
+ });
+ }
+ }
+ return cases;
+}
+
+g.test('bitwise_or').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 | e2: T
+T is i32, u32, vecN<i32>, or vecN<u32>
+
+Bitwise-or. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const type = scalarType(t.params.type);
+ const cases = makeBitwiseOrCases(t.params.type);
+
+ await run(t, binary('|'), [type, type], type, t.params, cases);
+});
+
+g.test('bitwise_or_compound').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 |= e2: T
+T is i32, u32, vecN<i32>, or vecN<u32>
+
+Bitwise-or. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const type = scalarType(t.params.type);
+ const cases = makeBitwiseOrCases(t.params.type);
+
+ await run(t, compoundBinary('|='), [type, type], type, t.params, cases);
+});
+
+function makeBitwiseAndCases(inputType) {
+ const V = inputType === 'i32' ? i32 : u32;
+ const cases = [
+ // Static patterns
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
+ expected: V(0b11111111111111111111111111111111)
+ },
+ {
+ input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b10100100010010100100010010100100), V(0b11111111111111111111111111111111)],
+ expected: V(0b10100100010010100100010010100100)
+ },
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b10100100010010100100010010100100)],
+ expected: V(0b10100100010010100100010010100100)
+ },
+ {
+ input: [V(0b01010010001001010010001001010010), V(0b01011011101101011011101101011011)],
+ expected: V(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)
+ });
+ }
+ }
+ return cases;
+}
+
+g.test('bitwise_and').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 & e2: T
+T is i32, u32, vecN<i32>, or vecN<u32>
+
+Bitwise-and. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const type = scalarType(t.params.type);
+ const cases = makeBitwiseAndCases(t.params.type);
+ await run(t, binary('&'), [type, type], type, t.params, cases);
+});
+
+g.test('bitwise_and_compound').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 &= e2: T
+T is i32, u32, vecN<i32>, or vecN<u32>
+
+Bitwise-and. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const type = scalarType(t.params.type);
+ const cases = makeBitwiseAndCases(t.params.type);
+ await run(t, compoundBinary('&='), [type, type], type, t.params, cases);
+});
+
+function makeBitwiseExcluseOrCases(inputType) {
+ const V = inputType === 'i32' ? i32 : u32;
+ const cases = [
+ // Static patterns
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
+ expected: V(0b11111111111111111111111111111111)
+ },
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
+ expected: V(0b11111111111111111111111111111111)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
+ expected: V(0b00000000000000000000000000000000)
+ },
+ {
+ input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
+ expected: V(0b10100100010010100100010010100100)
+ },
+ {
+ input: [V(0b10100100010010100100010010100100), V(0b11111111111111111111111111111111)],
+ expected: V(0b01011011101101011011101101011011)
+ },
+ {
+ input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
+ expected: V(0b10100100010010100100010010100100)
+ },
+ {
+ input: [V(0b11111111111111111111111111111111), V(0b10100100010010100100010010100100)],
+ expected: V(0b01011011101101011011101101011011)
+ },
+ {
+ input: [V(0b01010010001001010010001001010010), V(0b01011011101101011011101101011011)],
+ expected: V(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)
+ });
+ }
+ }
+ return cases;
+}
+
+g.test('bitwise_exclusive_or').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 ^ e2: T
+T is i32, u32, vecN<i32>, or vecN<u32>
+
+Bitwise-exclusive-or. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const type = scalarType(t.params.type);
+ const cases = makeBitwiseExcluseOrCases(t.params.type);
+ await run(t, binary('^'), [type, type], type, t.params, cases);
+});
+
+g.test('bitwise_exclusive_or_compound').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 ^= e2: T
+T is i32, u32, vecN<i32>, or vecN<u32>
+
+Bitwise-exclusive-or. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const type = scalarType(t.params.type);
+ const cases = makeBitwiseExcluseOrCases(t.params.type);
+ await run(t, compoundBinary('^='), [type, type], type, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise_shift.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise_shift.spec.js
new file mode 100644
index 0000000000..feed24b687
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bitwise_shift.spec.js
@@ -0,0 +1,343 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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, TypeU32, u32 } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function is_unsiged(type) {
+ return type === 'u32';
+}
+
+const bitwidth = 32;
+
+// Returns true if e1 << e2 is valid for const evaluation
+function is_valid_const_shift_left(e1, e1Type, e2) {
+ // Shift by 0 is always valid
+ if (e2 === 0) {
+ return true;
+ }
+
+ // Cannot shift by bitwidth or greater
+ if (e2 >= bitwidth) {
+ return false;
+ }
+
+ if (is_unsiged(e1Type)) {
+ // If T is an unsigned integer type, and any of the e2 most significant bits of e1 are 1, then invalid.
+ const must_be_zero_msb = e2;
+ const mask = ~0 << bitwidth - must_be_zero_msb;
+ if ((e1 & mask) !== 0) {
+ return false;
+ }
+ } else {
+ // If T is a signed integer type, and the e2+1 most significant bits of e1 do
+ // not have the same bit value, then error.
+ const must_match_msb = e2 + 1;
+ const mask = ~0 << bitwidth - must_match_msb;
+ if ((e1 & mask) !== 0 && (e1 & mask) !== mask) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Returns true if e1 >> e2 is valid for const evaluation
+function is_valid_const_shift_right(e1, e1Type, e2) {
+ // Shift by 0 is always valid
+ if (e2 === 0) {
+ return true;
+ }
+
+ // Cannot shift by bitwidth or greater
+ if (e2 >= bitwidth) {
+ return false;
+ }
+
+ return true;
+}
+
+// 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, e1Type, is_const) {
+ const V = e1Type === 'i32' ? i32 : u32;
+ const cases = [];
+ for (let shift = 0; shift < 64; ++shift) {
+ const e2 = shift;
+ if (is_const && !is_valid_const_shift_left(e1, e1Type, e2)) {
+ continue;
+ }
+ const expected = e1 << e2 % bitwidth;
+ cases.push({ input: [V(e1), u32(e2)], expected: V(expected) });
+ }
+ return cases;
+}
+
+// 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, e1Type, is_const) {
+ const V = e1Type === 'i32' ? i32 : u32;
+ const cases = [];
+ for (let shift = 0; shift < 64; ++shift) {
+ const e2 = shift;
+ if (is_const && !is_valid_const_shift_right(e1, e1Type, e2)) {
+ continue;
+ }
+
+ let expected = 0;
+ if (is_unsiged(e1Type)) {
+ // zero-fill right shift
+ expected = e1 >>> e2;
+ } else {
+ // arithmetic right shift
+ expected = e1 >> e2;
+ }
+ cases.push({ input: [V(e1), u32(e2)], expected: V(expected) });
+ }
+ return cases;
+}
+
+function makeShiftLeftConcreteCases(inputType, inputSource, type) {
+ const V = inputType === 'i32' ? i32 : u32;
+ const is_const = inputSource === 'const';
+
+ const cases = [
+ {
+ input: /* */[V(0b00000000000000000000000000000001), u32(1)],
+ expected: /**/V(0b00000000000000000000000000000010)
+ },
+ {
+ input: /* */[V(0b00000000000000000000000000000011), u32(1)],
+ expected: /**/V(0b00000000000000000000000000000110)
+ }];
+
+
+ const add_unsigned_overflow_cases = !is_const || is_unsiged(inputType);
+ const add_signed_overflow_cases = !is_const || !is_unsiged(inputType);
+
+ if (add_unsigned_overflow_cases) {
+ // Cases that are fine for unsigned values, but would overflow (sign change) signed
+ // values when const evaluated.
+ cases.push(
+ ...[
+ {
+ input: [/* */V(0b01000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b10000000000000000000000000000000)
+ },
+ {
+ input: [/* */V(0b01111111111111111111111111111111), u32(1)],
+ expected: /**/V(0b11111111111111111111111111111110)
+ },
+ {
+ input: [/* */V(0b00000000000000000000000000000001), u32(31)],
+ expected: /**/V(0b10000000000000000000000000000000)
+ }]
+
+ );
+ }
+ if (add_signed_overflow_cases) {
+ // Cases that are fine for signed values (no sign change), but would overflow
+ // unsigned values when const evaluated.
+ cases.push(
+ ...[
+ {
+ input: [/* */V(0b11000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b10000000000000000000000000000000)
+ },
+ {
+ input: [/* */V(0b11111111111111111111111111111111), u32(1)],
+ expected: /**/V(0b11111111111111111111111111111110)
+ },
+ {
+ input: [/* */V(0b11111111111111111111111111111111), u32(31)],
+ expected: /**/V(0b10000000000000000000000000000000)
+ }]
+
+ );
+ }
+
+ // Generate cases that shift input value by [0,63] (invalid const eval cases are not returned).
+ cases.push(...generate_shift_left_cases(0b00000000000000000000000000000000, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b00000000000000000000000000000001, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b00000000000000000000000000000010, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b00000000000000000000000000000011, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b10000000000000000000000000000000, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b01000000000000000000000000000000, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b11000000000000000000000000000000, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b00010000001000001000010001010101, inputType, is_const));
+ cases.push(...generate_shift_left_cases(0b11101111110111110111101110101010, inputType, is_const));
+ return cases;
+}
+
+g.test('shift_left_concrete').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 << e2
+
+Shift left (shifted value is concrete)
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+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);
+});
+
+g.test('shift_left_concrete_compound').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 <<= e2
+
+Shift left (shifted value is concrete)
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+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);
+});
+
+function makeShiftRightConcreteCases(inputType, inputSource, type) {
+ const V = inputType === 'i32' ? i32 : u32;
+ const is_const = inputSource === 'const';
+
+ const cases = [
+ {
+ input: /* */[V(0b00000000000000000000000000000001), u32(1)],
+ expected: /**/V(0b00000000000000000000000000000000)
+ },
+ {
+ input: /* */[V(0b00000000000000000000000000000011), u32(1)],
+ expected: /**/V(0b00000000000000000000000000000001)
+ },
+ {
+ input: /* */[V(0b01000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b00100000000000000000000000000000)
+ },
+ {
+ input: /* */[V(0b01100000000000000000000000000000), u32(1)],
+ expected: /**/V(0b00110000000000000000000000000000)
+ }];
+
+ if (is_unsiged(inputType)) {
+ // No sign extension
+ cases.push(
+ ...[
+ {
+ input: /* */[V(0b10000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b01000000000000000000000000000000)
+ },
+ {
+ input: /* */[V(0b11000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b01100000000000000000000000000000)
+ }]
+
+ );
+ } else {
+ cases.push(
+ // Sign extension if msb is 1
+ ...[
+ {
+ input: /* */[V(0b10000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b11000000000000000000000000000000)
+ },
+ {
+ input: /* */[V(0b11000000000000000000000000000000), u32(1)],
+ expected: /**/V(0b11100000000000000000000000000000)
+ }]
+
+ );
+ }
+
+ // Generate cases that shift input value by [0,63] (invalid const eval cases are not returned).
+ cases.push(
+ ...generate_shift_right_cases(0b00000000000000000000000000000000, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b00000000000000000000000000000001, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b00000000000000000000000000000010, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b00000000000000000000000000000011, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b10000000000000000000000000000000, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b01000000000000000000000000000000, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b11000000000000000000000000000000, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b00010000001000001000010001010101, inputType, is_const)
+ );
+ cases.push(
+ ...generate_shift_right_cases(0b11101111110111110111101110101010, inputType, is_const)
+ );
+ return cases;
+}
+
+g.test('shift_right_concrete').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 >> e2
+
+Shift right (shifted value is concrete)
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+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);
+});
+
+g.test('shift_right_concrete_compound').
+specURL('https://www.w3.org/TR/WGSL/#bit-expr').
+desc(
+ `
+e1 >>= e2
+
+Shift right (shifted value is concrete)
+`
+).
+params((u) =>
+u.
+combine('type', ['i32', 'u32']).
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bool_logical.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bool_logical.spec.js
new file mode 100644
index 0000000000..52cbe17222
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/bool_logical.spec.js
@@ -0,0 +1,187 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Short circuiting vs no short circuiting is not tested here, it is covered in
+// src/webgpu/shader/execution/evaluation_order.spec.ts
+
+g.test('and').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 & e2
+Logical "and". Component-wise when T is a vector. Evaluates both e1 and e2.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(false) },
+ { input: [bool(false), bool(true)], expected: bool(false) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, binary('&'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('and_compound').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 &= e2
+Logical "and". Component-wise when T is a vector. Evaluates both e1 and e2.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(false) },
+ { input: [bool(false), bool(true)], expected: bool(false) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, compoundBinary('&='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('and_short_circuit').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 && e2
+short_circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 only if e1 is true.
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(false) },
+ { input: [bool(false), bool(true)], expected: bool(false) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, binary('&&'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('or').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 | e2
+Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(true) },
+ { input: [bool(false), bool(true)], expected: bool(true) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, binary('|'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('or_compound').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 |= e2
+Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(true) },
+ { input: [bool(false), bool(true)], expected: bool(true) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, compoundBinary('|='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('or_short_circuit').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 || e2
+short_circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 only if e1 is true.
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(true) },
+ { input: [bool(false), bool(true)], expected: bool(true) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, binary('||'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('equals').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 == e2
+Equality. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(true) },
+ { input: [bool(true), bool(false)], expected: bool(false) },
+ { input: [bool(false), bool(true)], expected: bool(false) },
+ { input: [bool(true), bool(true)], expected: bool(true) }];
+
+
+ await run(t, binary('=='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('not_equals').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: e1 != e2
+Equality. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: [bool(false), bool(false)], expected: bool(false) },
+ { input: [bool(true), bool(false)], expected: bool(true) },
+ { input: [bool(false), bool(true)], expected: bool(true) },
+ { input: [bool(true), bool(true)], expected: bool(false) }];
+
+
+ await run(t, binary('!='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_addition.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_addition.spec.js
new file mode 100644
index 0000000000..28087a1ca3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_addition.spec.js
@@ -0,0 +1,212 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const additionVectorScalarInterval = (v, s) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s, v) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.additionInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x + y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x += y
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('+='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('+'),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x += y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('+='),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('+'),
+ [TypeF16, TypeVec(dim, TypeF16)],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_comparison.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_comparison.spec.js
new file mode 100644
index 0000000000..33b99a219b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_comparison.spec.js
@@ -0,0 +1,280 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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, TypeBool, TypeF16 } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF16, vectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, 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 truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+lhs,
+rhs,
+truthFunc)
+{
+ // 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 = [];
+ 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, rhs) => {
+ return lhs.value === rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value === rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value !== rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value !== rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value < rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value < rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value <= rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value <= rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value > rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value > rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value >= rhs.value;
+ };
+
+ return vectorF16Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value >= rhs.value;
+ };
+
+ 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(
+ `
+Expression: x == y
+Accuracy: Correct result
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'equals_const' : 'equals_non_const'
+ );
+ await run(t, binary('=='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+});
+
+g.test('not_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x != y
+Accuracy: Correct result
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('less_than').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x < y
+Accuracy: Correct result
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('less_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x <= y
+Accuracy: Correct result
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('greater_than').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x > y
+Accuracy: Correct result
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('greater_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x >= y
+Accuracy: Correct result
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_division.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_division.spec.js
new file mode 100644
index 0000000000..bcbb523c0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_division.spec.js
@@ -0,0 +1,212 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const divisionVectorScalarInterval = (v, s) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s, v) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.divisionInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x / y, where x and y are scalars
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x and y are vectors
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x /= y
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('/='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('/'),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x /= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('/='),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('/'),
+ [TypeF16, TypeVec(dim, TypeF16)],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.js
new file mode 100644
index 0000000000..9948b0d164
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_[non_]const
+const mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x + y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('+'),
+ [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x =+ y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('+='),
+ [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.js
new file mode 100644
index 0000000000..5076e979ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.js
@@ -0,0 +1,114 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matKxR_matCxK_[non_]const
+const mat_mat_cases = [2, 3, 4].
+flatMap((k) =>
+[2, 3, 4].flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x * y, where x is a matrix and y is a matrix
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('common_dim', [2, 3, 4]).
+combine('x_rows', [2, 3, 4]).
+combine('y_cols', [2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+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(
+ t.params.inputSource === 'const' ?
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_const` :
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeMat(x_cols, x_rows, TypeF16), TypeMat(y_cols, y_rows, TypeF16)],
+ TypeMat(y_cols, x_rows, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_matrix_compound').
+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', allInputSources).
+combine('common_dim', [2, 3, 4]).
+combine('x_rows', [2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const x_cols = t.params.common_dim;
+ const x_rows = t.params.x_rows;
+ const y_cols = x_cols;
+ const y_rows = t.params.common_dim;
+
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_const` :
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeMat(x_cols, x_rows, TypeF16), TypeMat(y_cols, y_rows, TypeF16)],
+ TypeMat(y_cols, x_rows, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.js
new file mode 100644
index 0000000000..292c7ad508
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.js
@@ -0,0 +1,161 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for matrix-scalar and scalar-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 { sparseF16Range, sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_scalar_[non_]const
+const mat_scalar_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x * y, where x is a matrix and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `mat${cols}x${rows}_scalar_const` :
+ `mat${cols}x${rows}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeMat(cols, rows, TypeF16), TypeF16],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_scalar_compound').
+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', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `mat${cols}x${rows}_scalar_const` :
+ `mat${cols}x${rows}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeMat(cols, rows, TypeF16), TypeF16],
+ TypeMat(cols, rows, TypeF16),
+ 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', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `scalar_mat${cols}x${rows}_const` :
+ `scalar_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeF16, TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.js
new file mode 100644
index 0000000000..c787e555be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_[non_]const
+const mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x - y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('-'),
+ [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('-='),
+ [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.js
new file mode 100644
index 0000000000..3cfcd5d4e1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.js
@@ -0,0 +1,156 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for matrix-vector and vector-matrix f16 multiplication expression
+`;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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_vecC_[non_]const
+const mat_vec_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((rows) =>
+[2, 3, 4].flatMap((cols) =>
+[true, false].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(
+ `
+Expression: x * y, where x is a matrix and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `mat${cols}x${rows}_vec${cols}_const` :
+ `mat${cols}x${rows}_vec${cols}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeMat(cols, rows, TypeF16), TypeVec(cols, TypeF16)],
+ TypeVec(rows, TypeF16),
+ 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', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `vec${rows}_mat${cols}x${rows}_const` :
+ `vec${rows}_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeVec(rows, TypeF16), TypeMat(cols, rows, TypeF16)],
+ TypeVec(cols, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_matrix_compound').
+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', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cols = t.params.dim;
+ const rows = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `vec${rows}_mat${cols}x${rows}_const` :
+ `vec${rows}_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeVec(rows, TypeF16), TypeMat(cols, rows, TypeF16)],
+ TypeVec(cols, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_multiplication.spec.js
new file mode 100644
index 0000000000..42310b240f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_multiplication.spec.js
@@ -0,0 +1,212 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const multiplicationVectorScalarInterval = (v, s) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s, v) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.multiplicationInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x * y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x *= y
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('*='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x *= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeF16, TypeVec(dim, TypeF16)],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_remainder.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_remainder.spec.js
new file mode 100644
index 0000000000..e03aeee248
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_remainder.spec.js
@@ -0,0 +1,212 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const remainderVectorScalarInterval = (v, s) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s, v) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.remainderInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x % y, where x and y are scalars
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('%'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x and y are vectors
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('%'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x %= y
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('%='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('%'),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x %= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('%='),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('%'),
+ [TypeF16, TypeVec(dim, TypeF16)],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_subtraction.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_subtraction.spec.js
new file mode 100644
index 0000000000..badf829b69
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f16_subtraction.spec.js
@@ -0,0 +1,212 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const subtractionVectorScalarInterval = (v, s) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s, v) => {
+ return FP.f16.toVector(v.map((e) => FP.f16.subtractionInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x - y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('-='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('-'),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('-='),
+ [TypeVec(dim, TypeF16), TypeF16],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('-'),
+ [TypeF16, TypeVec(dim, TypeF16)],
+ TypeVec(dim, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_addition.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_addition.spec.js
new file mode 100644
index 0000000000..a4dff15436
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_addition.spec.js
@@ -0,0 +1,194 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const additionVectorScalarInterval = (v, s) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s, v) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.additionInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x + y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x += y
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('+='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('+'),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x += y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('+='),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x + y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('+'),
+ [TypeF32, TypeVec(dim, TypeF32)],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_comparison.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_comparison.spec.js
new file mode 100644
index 0000000000..dddf58cb3f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_comparison.spec.js
@@ -0,0 +1,262 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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, TypeBool, TypeF32 } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF32, vectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, 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 truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+lhs,
+rhs,
+truthFunc)
+{
+ // 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 = [];
+ 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, rhs) => {
+ return lhs.value === rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value === rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value !== rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value !== rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value < rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value < rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value <= rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value <= rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value > rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value > rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_non_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value >= rhs.value;
+ };
+
+ return vectorF32Range(2).map((v) => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_const: () => {
+ const truthFunc = (lhs, rhs) => {
+ return lhs.value >= rhs.value;
+ };
+
+ 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(
+ `
+Expression: x == y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'equals_const' : 'equals_non_const'
+ );
+ await run(t, binary('=='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+});
+
+g.test('not_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x != y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('less_than').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x < y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('less_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x <= y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('greater_than').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x > y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+});
+
+g.test('greater_equals').
+specURL('https://www.w3.org/TR/WGSL/#comparison-expr').
+desc(
+ `
+Expression: x >= y
+Accuracy: Correct result
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_division.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_division.spec.js
new file mode 100644
index 0000000000..66573b123c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_division.spec.js
@@ -0,0 +1,194 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const divisionVectorScalarInterval = (v, s) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s, v) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.divisionInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x / y, where x and y are scalars
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x and y are vectors
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x /= y
+Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('/='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('/'),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x /= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('/='),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('/'),
+ [TypeF32, TypeVec(dim, TypeF32)],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.js
new file mode 100644
index 0000000000..f203990550
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.js
@@ -0,0 +1,95 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_[non_]const
+const mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x + y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('+'),
+ [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x =+ y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('+='),
+ [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.js
new file mode 100644
index 0000000000..bebbaa701a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.js
@@ -0,0 +1,108 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matKxR_matCxK_[non_]const
+const mat_mat_cases = [2, 3, 4].
+flatMap((k) =>
+[2, 3, 4].flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x * y, where x is a matrix and y is a matrix
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('common_dim', [2, 3, 4]).
+combine('x_rows', [2, 3, 4]).
+combine('y_cols', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_const` :
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeMat(x_cols, x_rows, TypeF32), TypeMat(y_cols, y_rows, TypeF32)],
+ TypeMat(y_cols, x_rows, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_matrix_compound').
+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', allInputSources).
+combine('common_dim', [2, 3, 4]).
+combine('x_rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const x_cols = t.params.common_dim;
+ const x_rows = t.params.x_rows;
+ const y_cols = x_cols;
+ const y_rows = t.params.common_dim;
+
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_const` :
+ `mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeMat(x_cols, x_rows, TypeF32), TypeMat(y_cols, y_rows, TypeF32)],
+ TypeMat(y_cols, x_rows, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.js
new file mode 100644
index 0000000000..324998e915
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.js
@@ -0,0 +1,152 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for matrix-scalar and scalar-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 { sparseF32Range, sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_scalar_[non_]const
+const mat_scalar_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x * y, where x is a matrix and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `mat${cols}x${rows}_scalar_const` :
+ `mat${cols}x${rows}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeMat(cols, rows, TypeF32), TypeF32],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_scalar_compound').
+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', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `mat${cols}x${rows}_scalar_const` :
+ `mat${cols}x${rows}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeMat(cols, rows, TypeF32), TypeF32],
+ TypeMat(cols, rows, TypeF32),
+ 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', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `scalar_mat${cols}x${rows}_const` :
+ `scalar_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeF32, TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.js
new file mode 100644
index 0000000000..c427c8d5bd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.js
@@ -0,0 +1,95 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_[non_]const
+const mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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(
+ `
+Expression: x - y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('-'),
+ [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('matrix_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y, where x and y are matrices
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `mat${cols}x${rows}_const` : `mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('-='),
+ [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.js
new file mode 100644
index 0000000000..8149f4a140
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.js
@@ -0,0 +1,147 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for matrix-vector and vector-matrix f32 multiplication expression
+`;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 { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: matCxR_vecC_[non_]const
+const mat_vec_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((rows) =>
+[2, 3, 4].flatMap((cols) =>
+[true, false].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(
+ `
+Expression: x * y, where x is a matrix and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `mat${cols}x${rows}_vec${cols}_const` :
+ `mat${cols}x${rows}_vec${cols}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeMat(cols, rows, TypeF32), TypeVec(cols, TypeF32)],
+ TypeVec(rows, TypeF32),
+ 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', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `vec${rows}_mat${cols}x${rows}_const` :
+ `vec${rows}_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeVec(rows, TypeF32), TypeMat(cols, rows, TypeF32)],
+ TypeVec(cols, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_matrix_compound').
+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', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const cols = t.params.dim;
+ const rows = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `vec${rows}_mat${cols}x${rows}_const` :
+ `vec${rows}_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeVec(rows, TypeF32), TypeMat(cols, rows, TypeF32)],
+ TypeVec(cols, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_multiplication.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_multiplication.spec.js
new file mode 100644
index 0000000000..dd60365d5d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_multiplication.spec.js
@@ -0,0 +1,194 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const multiplicationVectorScalarInterval = (v, s) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s, v) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.multiplicationInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x * y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x *= y
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('*='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x *= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('*='),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('*'),
+ [TypeF32, TypeVec(dim, TypeF32)],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_remainder.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_remainder.spec.js
new file mode 100644
index 0000000000..2b569180ee
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_remainder.spec.js
@@ -0,0 +1,194 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const remainderVectorScalarInterval = (v, s) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s, v) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.remainderInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x % y, where x and y are scalars
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x and y are vectors
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x %= y
+Accuracy: Derived from x - y * trunc(x/y)
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('%='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('%'),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x %= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('%='),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('%'),
+ [TypeF32, TypeVec(dim, TypeF32)],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_subtraction.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_subtraction.spec.js
new file mode 100644
index 0000000000..fe3ed8783a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/f32_subtraction.spec.js
@@ -0,0 +1,194 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../../../../util/floating_point.js';
+import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { binary, compoundBinary } from './binary.js';
+
+const subtractionVectorScalarInterval = (v, s) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s, v) => {
+ return FP.f32.toVector(v.map((e) => FP.f32.subtractionInterval(s, e)));
+};
+
+export const g = makeTestGroup(GPUTest);
+
+const scalar_cases = [true, false].
+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].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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(
+ `
+Expression: x - y, where x and y are scalars
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x and y are vectors
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const' // Using vectorize to generate vector cases based on scalar cases
+ );
+ await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
+ );
+ await run(t, compoundBinary('-='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ binary('-'),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y, where x is a vector and y is a scalar
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `vec${dim}_scalar_const` : `vec${dim}_scalar_non_const`
+ );
+ await run(
+ t,
+ compoundBinary('-='),
+ [TypeVec(dim, TypeF32), TypeF32],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y, where x is a scalar and y is a vector
+Accuracy: Correctly rounded
+`
+).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? `scalar_vec${dim}_const` : `scalar_vec${dim}_non_const`
+ );
+ await run(
+ t,
+ binary('-'),
+ [TypeF32, TypeVec(dim, TypeF32)],
+ TypeVec(dim, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.js
new file mode 100644
index 0000000000..57f03df3da
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.js
@@ -0,0 +1,738 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { binary, compoundBinary } from './binary.js';
+
+function i32_add(x, y) {
+ return x + y;
+}
+
+function i32_subtract(x, y) {
+ return x - y;
+}
+
+function i32_multiply(x, y) {
+ return Math.imul(x, y);
+}
+
+function i32_divide_non_const(x, y) {
+ if (y === 0) {
+ return x;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return x;
+ }
+ return x / y;
+}
+
+function i32_divide_const(x, y) {
+ if (y === 0) {
+ return undefined;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return undefined;
+ }
+ return x / y;
+}
+
+function i32_remainder_non_const(x, y) {
+ if (y === 0) {
+ return 0;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return 0;
+ }
+ return x % y;
+}
+
+function i32_remainder_const(x, y) {
+ if (y === 0) {
+ return undefined;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return undefined;
+ }
+ return x % y;
+}
+
+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(
+ `
+Expression: x + y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('addition');
+ await run(t, binary('+'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('addition_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x += y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('addition');
+ await run(t, compoundBinary('+='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('subtraction').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x - y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('subtraction');
+ await run(t, binary('-'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('subtraction_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x -= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('subtraction');
+ await run(t, compoundBinary('-='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('multiplication').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x * y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('multiplication');
+ await run(t, binary('*'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('multiplication_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x *= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('multiplication');
+ await run(t, compoundBinary('*='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('division').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x / y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
+ );
+ await run(t, binary('/'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('division_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x /= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
+ );
+ await run(t, compoundBinary('/='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('remainder').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x % y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
+ );
+ await run(t, binary('%'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('remainder_compound').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: x %= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
+ );
+ await run(t, compoundBinary('%='), [TypeI32, TypeI32], TypeI32, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`addition_scalar_vector${vec_size}`);
+ await run(t, binary('+'), [TypeI32, 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', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`addition_vector${vec_size}_scalar`);
+ await run(t, binary('+'), [vec_type, TypeI32], vec_type, t.params, cases);
+});
+
+g.test('addition_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x += y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`addition_vector${vec_size}_scalar`);
+ await run(t, compoundBinary('+='), [vec_type, TypeI32], vec_type, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
+ await run(t, binary('-'), [TypeI32, 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', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
+ await run(t, binary('-'), [vec_type, TypeI32], vec_type, t.params, cases);
+});
+
+g.test('subtraction_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x -= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
+ await run(t, compoundBinary('-='), [vec_type, TypeI32], vec_type, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
+ await run(t, binary('*'), [TypeI32, 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', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
+ await run(t, binary('*'), [vec_type, TypeI32], vec_type, t.params, cases);
+});
+
+g.test('multiplication_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x *= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
+ await run(t, compoundBinary('*='), [vec_type, TypeI32], vec_type, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ 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);
+});
+
+g.test('division_vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x / y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ 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);
+});
+
+g.test('division_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x /= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ 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);
+});
+
+g.test('remainder_scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x % y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ 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);
+});
+
+g.test('remainder_vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x % y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ 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);
+});
+
+g.test('remainder_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x %= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeI32);
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_comparison.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_comparison.spec.js
new file mode 100644
index 0000000000..939bb51cf2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/i32_comparison.spec.js
@@ -0,0 +1,121 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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, 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, rhs, expected_answer) {
+ 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(
+ `
+Expression: x == y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('equals');
+ await run(t, binary('=='), [TypeI32, TypeI32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('not_equals');
+ await run(t, binary('!='), [TypeI32, TypeI32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('less_than');
+ await run(t, binary('<'), [TypeI32, TypeI32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('less_equal');
+ await run(t, binary('<='), [TypeI32, TypeI32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('greater_than');
+ await run(t, binary('>'), [TypeI32, TypeI32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('greater_equal');
+ await run(t, binary('>='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.js
new file mode 100644
index 0000000000..32fd44b474
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.js
@@ -0,0 +1,725 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { binary, compoundBinary } from './binary.js';
+
+function u32_add(x, y) {
+ return x + y;
+}
+
+function u32_subtract(x, y) {
+ return x - y;
+}
+
+function u32_multiply(x, y) {
+ return Math.imul(x, y);
+}
+
+function u32_divide_non_const(x, y) {
+ if (y === 0) {
+ return x;
+ }
+ return x / y;
+}
+
+function u32_divide_const(x, y) {
+ if (y === 0) {
+ return undefined;
+ }
+ return x / y;
+}
+
+function u32_remainder_non_const(x, y) {
+ if (y === 0) {
+ return 0;
+ }
+ return x % y;
+}
+
+function u32_remainder_const(x, y) {
+ if (y === 0) {
+ return undefined;
+ }
+ return x % y;
+}
+
+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(
+ `
+Expression: x + y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('addition');
+ await run(t, binary('+'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('addition_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x += y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('addition');
+ await run(t, compoundBinary('+='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('subtraction').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x - y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('subtraction');
+ await run(t, binary('-'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('subtraction_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x -= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('subtraction');
+ await run(t, compoundBinary('-='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('multiplication').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x * y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('multiplication');
+ await run(t, binary('*'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('multiplication_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x *= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('multiplication');
+ await run(t, compoundBinary('*='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('division').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x / y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
+ );
+ await run(t, binary('/'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('division_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x /= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
+ );
+ await run(t, compoundBinary('/='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('remainder').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x % y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
+ );
+ await run(t, binary('%'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('remainder_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x %= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
+ );
+ await run(t, compoundBinary('%='), [TypeU32, TypeU32], TypeU32, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`addition_scalar_vector${vec_size}`);
+ await run(t, binary('+'), [TypeU32, 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', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`addition_vector${vec_size}_scalar`);
+ await run(t, binary('+'), [vec_type, TypeU32], vec_type, t.params, cases);
+});
+
+g.test('addition_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x += y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`addition_vector${vec_size}_scalar`);
+ await run(t, compoundBinary('+='), [vec_type, TypeU32], vec_type, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
+ await run(t, binary('-'), [TypeU32, 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', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
+ await run(t, binary('-'), [vec_type, TypeU32], vec_type, t.params, cases);
+});
+
+g.test('subtraction_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x -= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
+ await run(t, compoundBinary('-='), [vec_type, TypeU32], vec_type, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
+ await run(t, binary('*'), [TypeU32, 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', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
+ await run(t, binary('*'), [vec_type, TypeU32], vec_type, t.params, cases);
+});
+
+g.test('multiplication_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x *= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
+ await run(t, compoundBinary('*='), [vec_type, TypeU32], vec_type, 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', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ 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);
+});
+
+g.test('division_vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x / y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ 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);
+});
+
+g.test('division_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x /= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ 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);
+});
+
+g.test('remainder_scalar_vector').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x % y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_rhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ 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);
+});
+
+g.test('remainder_vector_scalar').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x % y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ 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);
+});
+
+g.test('remainder_vector_scalar_compound').
+specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr').
+desc(
+ `
+Expression: x %= y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize_lhs', [2, 3, 4])
+).
+fn(async (t) => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = TypeVec(vec_size, TypeU32);
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_comparison.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_comparison.spec.js
new file mode 100644
index 0000000000..20bf128901
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/binary/u32_comparison.spec.js
@@ -0,0 +1,121 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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, 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, rhs, expected_answer) {
+ 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(
+ `
+Expression: x == y
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('equals');
+ await run(t, binary('=='), [TypeU32, TypeU32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('not_equals');
+ await run(t, binary('!='), [TypeU32, TypeU32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('less_than');
+ await run(t, binary('<'), [TypeU32, TypeU32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('less_equal');
+ await run(t, binary('<='), [TypeU32, TypeU32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('greater_than');
+ await run(t, binary('>'), [TypeU32, TypeU32], TypeBool, 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', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('greater_equal');
+ await run(t, binary('>='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/abs.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/abs.spec.js
new file mode 100644
index 0000000000..d6f843e50a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/abs.spec.js
@@ -0,0 +1,196 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'abs' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn abs(e: T ) -> T
+The absolute value of e. Component-wise when T is a vector. If e is a signed
+integral scalar type and evaluates to the largest negative value, then the
+result is e. If e is an unsigned integral type, then the result is e.
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn abs(e: T ) -> T
+Returns the absolute value of e (e.g. e with a positive sign bit).
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kBit } from '../../../../../util/constants.js';
+import {
+ i32Bits,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ u32Bits,
+ TypeAbstractFloat } from
+'../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('abs', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.absInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.absInterval);
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'unfiltered',
+ FP.abstract.absInterval
+ );
+ }
+});
+
+g.test('abstract_int').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`abstract int tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`unsigned int tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ await run(t, builtin('abs'), [TypeU32], TypeU32, t.params, [
+ // Min and Max u32
+ { input: u32Bits(kBit.u32.min), expected: u32Bits(kBit.u32.min) },
+ { input: u32Bits(kBit.u32.max), expected: u32Bits(kBit.u32.max) },
+ // Powers of 2: -2^i: 0 =< i =< 31
+ { input: u32Bits(kBit.powTwo.to0), expected: u32Bits(kBit.powTwo.to0) },
+ { input: u32Bits(kBit.powTwo.to1), expected: u32Bits(kBit.powTwo.to1) },
+ { input: u32Bits(kBit.powTwo.to2), expected: u32Bits(kBit.powTwo.to2) },
+ { input: u32Bits(kBit.powTwo.to3), expected: u32Bits(kBit.powTwo.to3) },
+ { input: u32Bits(kBit.powTwo.to4), expected: u32Bits(kBit.powTwo.to4) },
+ { input: u32Bits(kBit.powTwo.to5), expected: u32Bits(kBit.powTwo.to5) },
+ { input: u32Bits(kBit.powTwo.to6), expected: u32Bits(kBit.powTwo.to6) },
+ { input: u32Bits(kBit.powTwo.to7), expected: u32Bits(kBit.powTwo.to7) },
+ { input: u32Bits(kBit.powTwo.to8), expected: u32Bits(kBit.powTwo.to8) },
+ { input: u32Bits(kBit.powTwo.to9), expected: u32Bits(kBit.powTwo.to9) },
+ { input: u32Bits(kBit.powTwo.to10), expected: u32Bits(kBit.powTwo.to10) },
+ { input: u32Bits(kBit.powTwo.to11), expected: u32Bits(kBit.powTwo.to11) },
+ { input: u32Bits(kBit.powTwo.to12), expected: u32Bits(kBit.powTwo.to12) },
+ { input: u32Bits(kBit.powTwo.to13), expected: u32Bits(kBit.powTwo.to13) },
+ { input: u32Bits(kBit.powTwo.to14), expected: u32Bits(kBit.powTwo.to14) },
+ { input: u32Bits(kBit.powTwo.to15), expected: u32Bits(kBit.powTwo.to15) },
+ { input: u32Bits(kBit.powTwo.to16), expected: u32Bits(kBit.powTwo.to16) },
+ { input: u32Bits(kBit.powTwo.to17), expected: u32Bits(kBit.powTwo.to17) },
+ { input: u32Bits(kBit.powTwo.to18), expected: u32Bits(kBit.powTwo.to18) },
+ { input: u32Bits(kBit.powTwo.to19), expected: u32Bits(kBit.powTwo.to19) },
+ { input: u32Bits(kBit.powTwo.to20), expected: u32Bits(kBit.powTwo.to20) },
+ { input: u32Bits(kBit.powTwo.to21), expected: u32Bits(kBit.powTwo.to21) },
+ { input: u32Bits(kBit.powTwo.to22), expected: u32Bits(kBit.powTwo.to22) },
+ { input: u32Bits(kBit.powTwo.to23), expected: u32Bits(kBit.powTwo.to23) },
+ { input: u32Bits(kBit.powTwo.to24), expected: u32Bits(kBit.powTwo.to24) },
+ { input: u32Bits(kBit.powTwo.to25), expected: u32Bits(kBit.powTwo.to25) },
+ { input: u32Bits(kBit.powTwo.to26), expected: u32Bits(kBit.powTwo.to26) },
+ { input: u32Bits(kBit.powTwo.to27), expected: u32Bits(kBit.powTwo.to27) },
+ { input: u32Bits(kBit.powTwo.to28), expected: u32Bits(kBit.powTwo.to28) },
+ { input: u32Bits(kBit.powTwo.to29), expected: u32Bits(kBit.powTwo.to29) },
+ { input: u32Bits(kBit.powTwo.to30), expected: u32Bits(kBit.powTwo.to30) },
+ { input: u32Bits(kBit.powTwo.to31), expected: u32Bits(kBit.powTwo.to31) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`signed int tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ await run(t, builtin('abs'), [TypeI32], TypeI32, t.params, [
+ // Min and max i32
+ // If e evaluates to the largest negative value, then the result is e.
+ { input: i32Bits(kBit.i32.negative.min), expected: i32Bits(kBit.i32.negative.min) },
+ { input: i32Bits(kBit.i32.negative.max), expected: i32Bits(kBit.i32.positive.min) },
+ { input: i32Bits(kBit.i32.positive.max), expected: i32Bits(kBit.i32.positive.max) },
+ { input: i32Bits(kBit.i32.positive.min), expected: i32Bits(kBit.i32.positive.min) },
+ // input: -1 * pow(2, n), n = {-31, ..., 0 }, expected: pow(2, n), n = {-31, ..., 0}]
+ { input: i32Bits(kBit.negPowTwo.to0), expected: i32Bits(kBit.powTwo.to0) },
+ { input: i32Bits(kBit.negPowTwo.to1), expected: i32Bits(kBit.powTwo.to1) },
+ { input: i32Bits(kBit.negPowTwo.to2), expected: i32Bits(kBit.powTwo.to2) },
+ { input: i32Bits(kBit.negPowTwo.to3), expected: i32Bits(kBit.powTwo.to3) },
+ { input: i32Bits(kBit.negPowTwo.to4), expected: i32Bits(kBit.powTwo.to4) },
+ { input: i32Bits(kBit.negPowTwo.to5), expected: i32Bits(kBit.powTwo.to5) },
+ { input: i32Bits(kBit.negPowTwo.to6), expected: i32Bits(kBit.powTwo.to6) },
+ { input: i32Bits(kBit.negPowTwo.to7), expected: i32Bits(kBit.powTwo.to7) },
+ { input: i32Bits(kBit.negPowTwo.to8), expected: i32Bits(kBit.powTwo.to8) },
+ { input: i32Bits(kBit.negPowTwo.to9), expected: i32Bits(kBit.powTwo.to9) },
+ { input: i32Bits(kBit.negPowTwo.to10), expected: i32Bits(kBit.powTwo.to10) },
+ { input: i32Bits(kBit.negPowTwo.to11), expected: i32Bits(kBit.powTwo.to11) },
+ { input: i32Bits(kBit.negPowTwo.to12), expected: i32Bits(kBit.powTwo.to12) },
+ { input: i32Bits(kBit.negPowTwo.to13), expected: i32Bits(kBit.powTwo.to13) },
+ { input: i32Bits(kBit.negPowTwo.to14), expected: i32Bits(kBit.powTwo.to14) },
+ { input: i32Bits(kBit.negPowTwo.to15), expected: i32Bits(kBit.powTwo.to15) },
+ { input: i32Bits(kBit.negPowTwo.to16), expected: i32Bits(kBit.powTwo.to16) },
+ { input: i32Bits(kBit.negPowTwo.to17), expected: i32Bits(kBit.powTwo.to17) },
+ { input: i32Bits(kBit.negPowTwo.to18), expected: i32Bits(kBit.powTwo.to18) },
+ { input: i32Bits(kBit.negPowTwo.to19), expected: i32Bits(kBit.powTwo.to19) },
+ { input: i32Bits(kBit.negPowTwo.to20), expected: i32Bits(kBit.powTwo.to20) },
+ { input: i32Bits(kBit.negPowTwo.to21), expected: i32Bits(kBit.powTwo.to21) },
+ { input: i32Bits(kBit.negPowTwo.to22), expected: i32Bits(kBit.powTwo.to22) },
+ { input: i32Bits(kBit.negPowTwo.to23), expected: i32Bits(kBit.powTwo.to23) },
+ { input: i32Bits(kBit.negPowTwo.to24), expected: i32Bits(kBit.powTwo.to24) },
+ { input: i32Bits(kBit.negPowTwo.to25), expected: i32Bits(kBit.powTwo.to25) },
+ { input: i32Bits(kBit.negPowTwo.to26), expected: i32Bits(kBit.powTwo.to26) },
+ { input: i32Bits(kBit.negPowTwo.to27), expected: i32Bits(kBit.powTwo.to27) },
+ { input: i32Bits(kBit.negPowTwo.to28), expected: i32Bits(kBit.powTwo.to28) },
+ { input: i32Bits(kBit.negPowTwo.to29), expected: i32Bits(kBit.powTwo.to29) },
+ { input: i32Bits(kBit.negPowTwo.to30), expected: i32Bits(kBit.powTwo.to30) },
+ { input: i32Bits(kBit.negPowTwo.to31), expected: i32Bits(kBit.powTwo.to31) }]
+ );
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(t, abstractBuiltin('abs'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`float 32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('abs'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('abs'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acos.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acos.spec.js
new file mode 100644
index 0000000000..c3970213ff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acos.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'acos' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn acos(e: T ) -> T
+Returns the arc cosine of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
+...fullF32Range()];
+
+
+const f16_inputs = [
+...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
+...fullF16Range()];
+
+
+export const d = makeCaseCache('acos', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.acosInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.acosInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.acosInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.acosInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('acos'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('acos'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acosh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acosh.spec.js
new file mode 100644
index 0000000000..a6d3d4728f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/acosh.spec.js
@@ -0,0 +1,81 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'acosh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn acosh(e: T ) -> T
+Returns the hyperbolic arc cosine of e. The result is 0 when e < 1.
+Computes the non-negative functional inverse of cosh.
+Component-wise when T is a vector.
+Note: The result is not mathematically meaningful when e < 1.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
+...fullF32Range()];
+
+const f16_inputs = [
+...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
+...fullF16Range()];
+
+
+export const d = makeCaseCache('acosh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', ...FP.f32.acoshIntervals);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', ...FP.f32.acoshIntervals);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', ...FP.f16.acoshIntervals);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', ...FP.f16.acoshIntervals);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('acosh'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('acosh'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/all.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/all.spec.js
new file mode 100644
index 0000000000..09bbb7c02e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/all.spec.js
@@ -0,0 +1,92 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'all' builtin function
+
+S is a bool
+T is S or vecN<S>
+@const fn all(e: T) -> bool
+Returns e if e is scalar.
+Returns true if each component of e is true if e is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ False,
+ True,
+ TypeBool,
+ TypeVec,
+ vec2,
+ vec3,
+ vec4 } from
+'../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions').
+desc(`bool tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'])
+).
+fn(async (t) => {
+ const overloads = {
+ scalar: {
+ type: TypeBool,
+ cases: [
+ { input: False, expected: False },
+ { input: True, expected: True }]
+
+ },
+ vec2: {
+ type: TypeVec(2, TypeBool),
+ cases: [
+ { input: vec2(False, False), expected: False },
+ { input: vec2(True, False), expected: False },
+ { input: vec2(False, True), expected: False },
+ { input: vec2(True, True), expected: True }]
+
+ },
+ vec3: {
+ type: TypeVec(3, TypeBool),
+ cases: [
+ { input: vec3(False, False, False), expected: False },
+ { input: vec3(True, False, False), expected: False },
+ { input: vec3(False, True, False), expected: False },
+ { input: vec3(True, True, False), expected: False },
+ { input: vec3(False, False, True), expected: False },
+ { input: vec3(True, False, True), expected: False },
+ { input: vec3(False, True, True), expected: False },
+ { input: vec3(True, True, True), expected: True }]
+
+ },
+ vec4: {
+ type: TypeVec(4, TypeBool),
+ cases: [
+ { input: vec4(False, False, False, False), expected: False },
+ { input: vec4(False, True, False, False), expected: False },
+ { input: vec4(False, False, True, False), expected: False },
+ { input: vec4(False, True, True, False), expected: False },
+ { input: vec4(False, False, False, True), expected: False },
+ { input: vec4(False, True, False, True), expected: False },
+ { input: vec4(False, False, True, True), expected: False },
+ { input: vec4(False, True, True, True), expected: False },
+ { input: vec4(True, False, False, False), expected: False },
+ { input: vec4(True, False, False, True), expected: False },
+ { input: vec4(True, False, True, False), expected: False },
+ { input: vec4(True, False, True, True), expected: False },
+ { input: vec4(True, True, False, False), expected: False },
+ { input: vec4(True, True, False, True), expected: False },
+ { input: vec4(True, True, True, False), expected: False },
+ { input: vec4(True, True, True, True), expected: True }]
+
+ }
+ };
+ const overload = overloads[t.params.overload];
+
+ await run(t, builtin('all'), [overload.type], TypeBool, t.params, overload.cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/any.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/any.spec.js
new file mode 100644
index 0000000000..119efbb2be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/any.spec.js
@@ -0,0 +1,92 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'any' builtin function
+
+S is a bool
+T is S or vecN<S>
+@const fn all(e) -> bool
+Returns e if e is scalar.
+Returns true if any component of e is true if e is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ False,
+ True,
+ TypeBool,
+ TypeVec,
+ vec2,
+ vec3,
+ vec4 } from
+'../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions').
+desc(`bool tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'])
+).
+fn(async (t) => {
+ const overloads = {
+ scalar: {
+ type: TypeBool,
+ cases: [
+ { input: False, expected: False },
+ { input: True, expected: True }]
+
+ },
+ vec2: {
+ type: TypeVec(2, TypeBool),
+ cases: [
+ { input: vec2(False, False), expected: False },
+ { input: vec2(True, False), expected: True },
+ { input: vec2(False, True), expected: True },
+ { input: vec2(True, True), expected: True }]
+
+ },
+ vec3: {
+ type: TypeVec(3, TypeBool),
+ cases: [
+ { input: vec3(False, False, False), expected: False },
+ { input: vec3(True, False, False), expected: True },
+ { input: vec3(False, True, False), expected: True },
+ { input: vec3(True, True, False), expected: True },
+ { input: vec3(False, False, True), expected: True },
+ { input: vec3(True, False, True), expected: True },
+ { input: vec3(False, True, True), expected: True },
+ { input: vec3(True, True, True), expected: True }]
+
+ },
+ vec4: {
+ type: TypeVec(4, TypeBool),
+ cases: [
+ { input: vec4(False, False, False, False), expected: False },
+ { input: vec4(False, True, False, False), expected: True },
+ { input: vec4(False, False, True, False), expected: True },
+ { input: vec4(False, True, True, False), expected: True },
+ { input: vec4(False, False, False, True), expected: True },
+ { input: vec4(False, True, False, True), expected: True },
+ { input: vec4(False, False, True, True), expected: True },
+ { input: vec4(False, True, True, True), expected: True },
+ { input: vec4(True, False, False, False), expected: True },
+ { input: vec4(True, False, False, True), expected: True },
+ { input: vec4(True, False, True, False), expected: True },
+ { input: vec4(True, False, True, True), expected: True },
+ { input: vec4(True, True, False, False), expected: True },
+ { input: vec4(True, True, False, True), expected: True },
+ { input: vec4(True, True, True, False), expected: True },
+ { input: vec4(True, True, True, True), expected: True }]
+
+ }
+ };
+ const overload = overloads[t.params.overload];
+
+ await run(t, builtin('any'), [overload.type], TypeBool, t.params, overload.cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.js
new file mode 100644
index 0000000000..460332e691
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/arrayLength.spec.js
@@ -0,0 +1,306 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'arrayLength' builtin function.
+
+fn arrayLength(e: ptr<storage,array<T>> ) -> u32
+Returns the number of elements in the runtime-sized array.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { align } from '../../../../../util/math.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// List of array element types to test.
+const kTestTypes = [
+{ type: 'u32', stride: 4 },
+{ type: 'i32', stride: 4 },
+{ type: 'f32', stride: 4 },
+{ type: 'f16', stride: 2 },
+{ type: 'vec2<u32>', stride: 8 },
+{ type: 'vec2<i32>', stride: 8 },
+{ type: 'vec2<f32>', stride: 8 },
+{ type: 'vec2<f16>', stride: 4 },
+{ type: 'vec3<u32>', stride: 16 },
+{ type: 'vec3<i32>', stride: 16 },
+{ type: 'vec3<f32>', stride: 16 },
+{ type: 'vec3<f16>', stride: 8 },
+{ type: 'vec4<u32>', stride: 16 },
+{ type: 'vec4<i32>', stride: 16 },
+{ type: 'vec4<f32>', stride: 16 },
+{ type: 'vec4<f16>', stride: 8 },
+{ type: 'mat2x2<f32>', stride: 16 },
+{ type: 'mat2x3<f32>', stride: 32 },
+{ type: 'mat2x4<f32>', stride: 32 },
+{ type: 'mat3x2<f32>', stride: 24 },
+{ type: 'mat3x3<f32>', stride: 48 },
+{ type: 'mat3x4<f32>', stride: 48 },
+{ type: 'mat4x2<f32>', stride: 32 },
+{ type: 'mat4x3<f32>', stride: 64 },
+{ type: 'mat4x4<f32>', stride: 64 },
+{ type: 'mat2x2<f16>', stride: 8 },
+{ type: 'mat2x3<f16>', stride: 16 },
+{ type: 'mat2x4<f16>', stride: 16 },
+{ type: 'mat3x2<f16>', stride: 12 },
+{ type: 'mat3x3<f16>', stride: 24 },
+{ type: 'mat3x4<f16>', stride: 24 },
+{ type: 'mat4x2<f16>', stride: 16 },
+{ type: 'mat4x3<f16>', stride: 32 },
+{ type: 'mat4x4<f16>', stride: 32 },
+{ type: 'atomic<u32>', stride: 4 },
+{ type: 'atomic<i32>', stride: 4 },
+{ type: 'array<u32,4>', stride: 16 },
+{ type: 'array<i32,4>', stride: 16 },
+{ type: 'array<f32,4>', stride: 16 },
+{ type: 'array<f16,4>', stride: 8 },
+// Structures - see declarations below.
+{ type: 'ElemStruct', stride: 4 },
+{ type: 'ElemStruct_ImplicitPadding', stride: 16 },
+{ type: 'ElemStruct_ExplicitPadding', stride: 32 }];
+
+
+// Declarations for structures used as array element types.
+const kWgslStructures = `
+struct ElemStruct { a : u32 }
+struct ElemStruct_ImplicitPadding { a : vec3<u32> }
+struct ElemStruct_ExplicitPadding { @align(32) a : u32 }
+`;
+
+/**
+ * Run a shader and check that the array length is correct.
+ *
+ * @param t The test object
+ * @param wgsl The shader source
+ * @param stride The stride in bytes of the array element type
+ * @param offset The offset in bytes of the array from the start of the binding
+ * @param buffer_size The size in bytes of the buffer to allocate
+ * @param binding_size The size in bytes of the binding
+ * @param binding_offset The offset in bytes of the binding
+ * @param expected The array of expected values after running the shader
+ */
+function runShaderTest(
+t,
+wgsl,
+stride,
+offset,
+buffer_size,
+binding_size,
+binding_offset)
+{
+ // Create the compute pipeline.
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Create the buffer that will contain the runtime-sized array.
+ const buffer = t.device.createBuffer({
+ size: buffer_size,
+ usage: GPUBufferUsage.STORAGE
+ });
+
+ // Create the buffer that will receive the array length.
+ const lengthBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+
+ // Set up bindings.
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer, size: binding_size, offset: binding_offset } },
+ { binding: 1, resource: { buffer: lengthBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check the length.
+ const length = (binding_size - offset) / stride;
+ t.expectGPUBufferValuesEqual(lengthBuffer, new Uint32Array([length]));
+}
+
+/**
+ * Test if a WGSL type string require using f16 extension.
+ *
+ * @param test_type The wgsl type for testing
+ */
+function typeRequiresF16(test_type) {
+ return test_type.includes('f16');
+}
+
+/**
+ * Generate the necessary wgsl header for tested type, especially for f16
+ *
+ * @param test_type The wgsl type for testing
+ */
+function shaderHeader(test_type) {
+ return typeRequiresF16(test_type) ? 'enable f16;\n\n' : '';
+}
+
+g.test('single_element').
+specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin').
+desc(
+ `Test the arrayLength() builtin with a binding that is just large enough for a single element.
+
+ Test parameters:
+ - type: The WGSL type to use as the array element type.
+ - stride: The stride in bytes of the array element type.
+ `
+).
+params((u) => u.combineWithParams(kTestTypes)).
+beforeAllSubcases((t) => {
+ if (typeRequiresF16(t.params.type)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const wgsl =
+ shaderHeader(t.params.type) +
+ kWgslStructures +
+ `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<${t.params.type}>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ let buffer_size = t.params.stride;
+ // Ensure that binding size is multiple of 4.
+ buffer_size = buffer_size + (~buffer_size + 1 & 3);
+ runShaderTest(t, wgsl, t.params.stride, 0, buffer_size, buffer_size, 0);
+});
+
+g.test('multiple_elements').
+specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin').
+desc(
+ `Test the arrayLength() builtin with a binding that is large enough for multiple elements.
+
+ We test sizes that are not exact multiples of the array element strides, to test that the
+ length is correctly floored to the next whole element.
+
+ Test parameters:
+ - buffer_size: The size in bytes of the buffer.
+ - type: The WGSL type to use as the array element type.
+ - stride: The stride in bytes of the array element type.
+ `
+).
+params((u) =>
+u.combine('buffer_size', [640, 1004, 1048576]).combineWithParams(kTestTypes)
+).
+beforeAllSubcases((t) => {
+ if (typeRequiresF16(t.params.type)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const wgsl =
+ shaderHeader(t.params.type) +
+ kWgslStructures +
+ `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<${t.params.type}>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ runShaderTest(t, wgsl, t.params.stride, 0, t.params.buffer_size, t.params.buffer_size, 0);
+});
+
+g.test('struct_member').
+specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin').
+desc(
+ `Test the arrayLength() builtin with an array that is inside a structure.
+
+ We include offsets that are not exact multiples of the array element strides, to test that
+ the length is correctly floored to the next whole element.
+
+ Test parameters:
+ - member_offset: The offset (in bytes) of the array member from the start of the struct.
+ - type: The WGSL type to use as the array element type.
+ - stride: The stride in bytes of the array element type.
+ `
+).
+params((u) => u.combine('member_offset', [0, 4, 20]).combineWithParams(kTestTypes)).
+beforeAllSubcases((t) => {
+ if (typeRequiresF16(t.params.type)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const member_offset = align(t.params.member_offset, t.params.stride);
+ const wgsl =
+ shaderHeader(t.params.type) +
+ kWgslStructures +
+ `
+ alias ArrayType = array<${t.params.type}>;
+ struct Struct {
+ ${t.params.member_offset > 0 ? `@size(${member_offset}) padding : u32,` : ``}
+ arr : ArrayType,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : Struct;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer.arr);
+ }
+ `;
+ const buffer_size = 1048576;
+ runShaderTest(t, wgsl, t.params.stride, member_offset, buffer_size, buffer_size, 0);
+});
+
+g.test('binding_subregion').
+specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin').
+desc(
+ `Test the arrayLength() builtin when used with a binding that starts at a non-zero offset and
+ does not fill the entire buffer.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<vec3<f32>>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ const stride = 16;
+ const buffer_size = 1024;
+ const binding_size = 640;
+ const binding_offset = 256;
+ runShaderTest(t, wgsl, stride, 0, buffer_size, binding_size, binding_offset);
+});
+
+g.test('read_only').
+specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin').
+desc(
+ `Test the arrayLength() builtin when used with a read-only storage buffer.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read> buffer : array<vec3<f32>>;
+ @group(0) @binding(1) var<storage, read_write> length : u32;
+ @compute @workgroup_size(1)
+ fn main() {
+ length = arrayLength(&buffer);
+ }
+ `;
+ const stride = 16;
+ const buffer_size = 1024;
+ runShaderTest(t, wgsl, stride, 0, buffer_size, buffer_size, 0);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asin.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asin.spec.js
new file mode 100644
index 0000000000..13461e409a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asin.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'asin' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn asin(e: T ) -> T
+Returns the arc sine of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
+...fullF32Range()];
+
+
+const f16_inputs = [
+...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
+...fullF16Range()];
+
+
+export const d = makeCaseCache('asin', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.asinInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.asinInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.asinInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.asinInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('asin'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('asin'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asinh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asinh.spec.js
new file mode 100644
index 0000000000..4460e16cf7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/asinh.spec.js
@@ -0,0 +1,65 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'sinh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn asinh(e: T ) -> T
+Returns the hyperbolic arc sine of e.
+Computes the functional inverse of sinh.
+Component-wise when T is a vector.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('asinh', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.asinhInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.asinhInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float test`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('asinh'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('asinh'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan.spec.js
new file mode 100644
index 0000000000..df6c63401b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan.spec.js
@@ -0,0 +1,80 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'atan' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn atan(e: T ) -> T
+Returns the arc tangent of e. Component-wise when T is a vector.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)];
+
+const f32_inputs = [...known_values, ...fullF32Range()];
+const f16_inputs = [...known_values, ...fullF16Range()];
+
+export const d = makeCaseCache('atan', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('atan'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('atan'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan2.spec.js
new file mode 100644
index 0000000000..d3b13d34c2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atan2.spec.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'atan2' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn atan2(e1: T ,e2: T ) -> T
+Returns the arc tangent of e1 over e2. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange, sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const cases = ['f32', 'f16'].
+flatMap((kind) =>
+[true, false].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])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(`f32_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
+ await run(t, builtin('atan2'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(`f16_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
+ await run(t, builtin('atan2'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atanh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atanh.spec.js
new file mode 100644
index 0000000000..85266368a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atanh.spec.js
@@ -0,0 +1,87 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'atanh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn atanh(e: T ) -> T
+Returns the hyperbolic arc tangent of e. The result is 0 when abs(e) ≥ 1.
+Computes the functional inverse of tanh.
+Component-wise when T is a vector.
+Note: The result is not mathematically meaningful when abs(e) >= 1.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const f32_inputs = [
+...biasedRange(kValue.f32.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
+-1,
+...biasedRange(kValue.f32.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
+1,
+...fullF32Range()];
+
+const f16_inputs = [
+...biasedRange(kValue.f16.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
+-1,
+...biasedRange(kValue.f16.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
+1,
+...fullF16Range()];
+
+
+export const d = makeCaseCache('atanh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanhInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanhInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanhInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanhInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('atanh'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('atanh'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.js
new file mode 100644
index 0000000000..3fc910b340
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, add and store value.
+
+ * Load the original value pointed to by atomic_ptr.
+ * Obtains a new value by adding with the value v.
+ * Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('add_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicAdd(&output[0], 1)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected[0] = numInvocations;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected
+ });
+});
+
+g.test('add_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicAdd(&wg[0], 1)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = t.params.workgroupSize;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.js
new file mode 100644
index 0000000000..ebc125b3fb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.js
@@ -0,0 +1,135 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, and and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by anding with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ kMapId,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('and_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+
+ // Allocate an output buffer with bitsize of max invocations plus 1 for validation
+ const bufferNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits high, then using atomicAnd to set mapped global id bit off.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0xffffffff;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicAnd(&output[i / 32], ~(${scalarType}(1) << i))
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(bufferNumElements).fill(initValue);
+ for (let id = 0; id < numInvocations; ++id) {
+ const i = mapId.f(id, numInvocations);
+ expected[Math.floor(i / 32)] &= ~(1 << i);
+ }
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+});
+
+g.test('and_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize;
+
+ // Allocate workgroup array with bitsize of max invocations plus 1 for validation
+ const wgNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits high, then using atomicAnd to set mapped global id bit off.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0xffffffff;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicAnd(&wg[i / 32], ~(${scalarType}(1) << i))
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize).fill(
+ initValue
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ for (let id = 0; id < numInvocations; ++id) {
+ const wg = expected.subarray(d * wgNumElements);
+ const i = mapId.f(id, numInvocations);
+ wg[Math.floor(i / 32)] &= ~(1 << i);
+ }
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.js
new file mode 100644
index 0000000000..60d1488c0b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.js
@@ -0,0 +1,742 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Performs the following steps atomically:
+ * Load the original value pointed to by atomic_ptr.
+ * Compare the original value to the value v using an equality operation.
+ * Store the value v only if the result of the equality comparison was true.
+
+Returns a two member structure, where the first member, old_value, is the original
+value of the atomic object and the second member, exchanged, is whether or not
+the comparison succeeded.
+
+Note: the equality comparison may spuriously fail on some implementations.
+That is, the second component of the result vector may be false even if the first
+component of the result vector equals cmp.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ typedArrayCtor,
+ kMapId,
+ onlyWorkgroupSizes } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('compare_exchange_weak_storage_basic').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> input : array<atomic<${scalarType}>>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @group(0) @binding(2)
+ var<storage, read_write> exchanged : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // Exchange every third value
+ var comp = id + 1;
+ if (id % 3 == 0) {
+ comp = id;
+ }
+ let r = atomicCompareExchangeWeak(&input[id], comp, map_id(id * 2));
+
+ // Store results
+ output[id] = r.old_value;
+ if (r.exchanged) {
+ exchanged[id] = 1;
+ } else {
+ exchanged[id] = 0;
+ }
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer with values [0..n]
+ const inputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(inputBuffer);
+ const data = new arrayType(inputBuffer.getMappedRange());
+ data.forEach((_, i) => data[i] = i);
+ inputBuffer.unmap();
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(exchangedBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ { binding: 2, resource: { buffer: exchangedBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial input buffer as it contains
+ // values returned from atomicCompareExchangeWeak
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ outputExpected.forEach((_, i) => outputExpected[i] = i);
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // Read back exchanged buffer
+ const exchangedBufferResult = await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT
+ });
+
+ // The input buffer should have been modified to a computed value for every third value,
+ // unless the comparison spuriously failed.
+ const inputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ inputExpected.forEach((_, i) => {
+ if (i % 3 === 0 && exchangedBufferResult.data[i]) {
+ inputExpected[i] = mapId.f(i * 2, numInvocations);
+ } else {
+ inputExpected[i] = i; // No change
+ }
+ });
+ t.expectGPUBufferValuesEqual(inputBuffer, inputExpected);
+});
+
+g.test('compare_exchange_weak_workgroup_basic').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> exchanged: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(2)
+ var<storage, read_write> wg_copy: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize wg[id] with this invocations global id
+ atomicStore(&wg[id], global_id);
+
+ // Exchange every third value
+ var comp = global_id + 1;
+ if (global_id % 3 == 0) {
+ comp = global_id;
+ }
+ let r = atomicCompareExchangeWeak(&wg[id], comp, map_id(global_id * 2));
+
+ // Store results
+ output[global_id] = r.old_value;
+ if (r.exchanged) {
+ exchanged[global_id] = 1;
+ } else {
+ exchanged[global_id] = 0;
+ }
+
+ // Copy new value into wg_copy
+ wg_copy[global_id] = atomicLoad(&wg[id]);
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const wgCopyBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(exchangedBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: exchangedBuffer } },
+ { binding: 2, resource: { buffer: wgCopyBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial wg buffer as it contains
+ // values returned from atomicCompareExchangeWeak
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(wgNumElements * dispatchSize);
+ outputExpected.forEach((_, i) => outputExpected[i] = i);
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // Read back exchanged buffer
+ const exchangedBufferResult = await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT
+ });
+
+ // And the wg copy buffer should have been modified to a computed value for every third value,
+ // unless the comparison spuriously failed.
+ const wgCopyBufferExpected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * dispatchSize
+ );
+ wgCopyBufferExpected.forEach((_, i) => {
+ if (i % 3 === 0 && exchangedBufferResult.data[i]) {
+ wgCopyBufferExpected[i] = mapId.f(i * 2, numInvocations);
+ } else {
+ wgCopyBufferExpected[i] = i; // No change
+ }
+ });
+ t.expectGPUBufferValuesEqual(wgCopyBuffer, wgCopyBufferExpected);
+});
+
+g.test('compare_exchange_weak_storage_advanced').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', onlyWorkgroupSizes) //
+.combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+
+ // Number of times each workgroup attempts to exchange the same value to the same memory address
+ const numWrites = 4;
+
+ const bufferNumElements = numInvocations * numWrites;
+ const pingPongValues = [24, 68];
+
+ const wgsl = `
+ @group(0) @binding(0)
+ var<storage, read_write> data : atomic<${scalarType}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> old_values : array<${scalarType}>;
+
+ @group(0) @binding(2)
+ var<storage, read_write> exchanged : array<${scalarType}>;
+
+ fn ping_pong_value(i: u32) -> ${scalarType} {
+ if (i % 2 == 0) {
+ return ${pingPongValues[0]};
+ } else {
+ return ${pingPongValues[1]};
+ }
+ }
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // Each invocation attempts to write an alternating (ping-pong) value, once per loop.
+ // The data value is initialized with the first of the two ping-pong values.
+ // Only one invocation per loop iteration should succeed. Note the workgroupBarrier() used
+ // to synchronize each invocation in the loop.
+ // The reason we alternate is in case atomicCompareExchangeWeak spurioulsy fails:
+ // If all invocations of one iteration spuriously fail, the very next iteration will also
+ // fail since the value will not have been exchanged; however, the subsequent one will succeed
+ // (assuming not all iterations spuriously fail yet again).
+
+ for (var i = 0u; i < ${numWrites}u; i++) {
+ let compare = ping_pong_value(i);
+ let next = ping_pong_value(i + 1);
+
+ let r = atomicCompareExchangeWeak(&data, compare, next);
+
+ let slot = i * ${numInvocations}u + u32(id);
+ old_values[slot] = r.old_value;
+ if (r.exchanged) {
+ exchanged[slot] = 1;
+ } else {
+ exchanged[slot] = 0;
+ }
+
+ workgroupBarrier();
+ }
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+ const defaultValue = 99999999;
+
+ // Create single-value data buffer initialized to the first ping-pong value
+ const dataBuffer = t.device.createBuffer({
+ size: 1 * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ {
+ const data = new arrayType(dataBuffer.getMappedRange());
+ data[0] = pingPongValues[0];
+ dataBuffer.unmap();
+ }
+ t.trackForCleanup(dataBuffer);
+
+ const oldValuesBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(oldValuesBuffer);
+ {
+ const data = new arrayType(oldValuesBuffer.getMappedRange());
+ data.fill(defaultValue);
+ oldValuesBuffer.unmap();
+ }
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(exchangedBuffer);
+ {
+ const data = new arrayType(exchangedBuffer.getMappedRange());
+ data.fill(defaultValue);
+ exchangedBuffer.unmap();
+ }
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: dataBuffer } },
+ { binding: 1, resource: { buffer: oldValuesBuffer } },
+ { binding: 2, resource: { buffer: exchangedBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const oldValuesBufferResult = (
+ await t.readGPUBufferRangeTyped(oldValuesBuffer, {
+ type: arrayType,
+ typedLength: oldValuesBuffer.size / arrayType.BYTES_PER_ELEMENT
+ })).
+ data;
+ const exchangedBufferResult = (
+ await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT
+ })).
+ data;
+
+ for (let w = 0; w < numWrites; ++w) {
+ const offset = w * numInvocations;
+ const exchanged = exchangedBufferResult.subarray(offset, offset + numInvocations);
+ const oldValues = oldValuesBufferResult.subarray(offset, offset + numInvocations);
+
+ const dumpValues = () => {
+ return `
+ For write: ${w}
+ exchanged: ${exchanged}
+ oldValues: ${oldValues}`;
+ };
+
+ // Only one of the invocations should have succeeded to exchange - or none if spurious failures occured
+ const noExchanges = exchanged.every((v) => v === 0);
+ if (noExchanges) {
+ // Spurious failure, all values in oldValues should be the default value
+ if (!oldValues.every((v) => v === defaultValue)) {
+ t.fail(
+ `Spurious failure detected, expected only default value of ${defaultValue} in oldValues buffer.${dumpValues()}`
+ );
+ return;
+ }
+ } else {
+ // Only one invocation should have exchanged its value
+ if (exchanged.filter((v) => v === 1).length !== 1) {
+ t.fail(`More than one invocation exchanged its value.${dumpValues()}`);
+ return;
+ }
+
+ // Get its index
+ const idx = exchanged.findIndex((v) => v === 1);
+ assert(idx !== -1);
+
+ // Its output should contain the old value after exchange
+ const oldValue = pingPongValues[w % 2];
+ if (oldValues[idx] !== oldValue) {
+ t.fail(
+ `oldValues[${idx}] expected to contain old value from exchange: ${oldValue}.${dumpValues()}'`
+ );
+ return;
+ }
+
+ // The rest of oldValues should either contain the old value or the newly exchanged value,
+ // depending on whether they executed atomicCompareExchangWeak before or after invocation 'idx'.
+ const oldValuesRest = oldValues.filter((_, i) => i !== idx);
+ if (!oldValuesRest.every((v) => pingPongValues.includes(v))) {
+ t.fail(
+ `Values in oldValues buffer should be one of '${pingPongValues}', except at index '${idx} where it is '${oldValue}'.${dumpValues()}`
+ );
+ return;
+ }
+ }
+ }
+});
+
+g.test('compare_exchange_weak_workgroup_advanced').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicCompareExchangeWeak(atomic_ptr: ptr<AS, atomic<T>, read_write>, cmp: T, v: T) -> __atomic_compare_exchange_result<T>
+
+struct __atomic_compare_exchange_result<T> {
+ old_value : T, // old value stored in the atomic
+ exchanged : bool, // true if the exchange was done
+}
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', onlyWorkgroupSizes) //
+.combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+
+ // Number of times each workgroup attempts to exchange the same value to the same memory address
+ const numWrites = 4;
+
+ const bufferNumElements = numInvocations * numWrites;
+ const pingPongValues = [24, 68];
+
+ const wgsl = `
+ var<workgroup> wg: atomic<${scalarType}>;
+
+ @group(0) @binding(0)
+ var<storage, read_write> old_values : array<${scalarType}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> exchanged : array<${scalarType}>;
+
+ fn ping_pong_value(i: u32) -> ${scalarType} {
+ if (i % 2 == 0) {
+ return ${pingPongValues[0]};
+ } else {
+ return ${pingPongValues[1]};
+ }
+ }
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+
+ // Each invocation attempts to write an alternating (ping-pong) value, once per loop.
+ // The input value is initialized with the first of the two ping-pong values.
+ // Only one invocation per loop iteration should succeed. Note the workgroupBarrier() used
+ // to synchronize each invocation in the loop.
+ // The reason we alternate is in case atomicCompareExchangeWeak spurioulsy fails:
+ // If all invocations of one iteration spuriously fail, the very next iteration will also
+ // fail since the value will not have been exchanged; however, the subsequent one will succeed
+ // (assuming not all iterations spuriously fail yet again).
+
+ // Initialize wg
+ if (local_invocation_index == 0) {
+ atomicStore(&wg, ${pingPongValues[0]});
+ }
+ workgroupBarrier();
+
+ for (var i = 0u; i < ${numWrites}u; i++) {
+ let compare = ping_pong_value(i);
+ let next = ping_pong_value(i + 1);
+
+ let r = atomicCompareExchangeWeak(&wg, compare, next);
+
+ let slot = i * ${numInvocations}u + u32(id);
+ old_values[slot] = r.old_value;
+ if (r.exchanged) {
+ exchanged[slot] = 1;
+ } else {
+ exchanged[slot] = 0;
+ }
+
+ workgroupBarrier();
+ }
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+ const defaultValue = 99999999;
+
+ const oldValuesBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(oldValuesBuffer);
+ {
+ const data = new arrayType(oldValuesBuffer.getMappedRange());
+ data.fill(defaultValue);
+ oldValuesBuffer.unmap();
+ }
+
+ const exchangedBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(exchangedBuffer);
+ {
+ const data = new arrayType(exchangedBuffer.getMappedRange());
+ data.fill(defaultValue);
+ exchangedBuffer.unmap();
+ }
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: oldValuesBuffer } },
+ { binding: 1, resource: { buffer: exchangedBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const oldValuesBufferResult = (
+ await t.readGPUBufferRangeTyped(oldValuesBuffer, {
+ type: arrayType,
+ typedLength: oldValuesBuffer.size / arrayType.BYTES_PER_ELEMENT
+ })).
+ data;
+ const exchangedBufferResult = (
+ await t.readGPUBufferRangeTyped(exchangedBuffer, {
+ type: arrayType,
+ typedLength: exchangedBuffer.size / arrayType.BYTES_PER_ELEMENT
+ })).
+ data;
+
+ for (let w = 0; w < numWrites; ++w) {
+ const offset = w * numInvocations;
+ const exchanged = exchangedBufferResult.subarray(offset, offset + numInvocations);
+ const oldValues = oldValuesBufferResult.subarray(offset, offset + numInvocations);
+
+ const dumpValues = () => {
+ return `
+ For write: ${w}
+ exchanged: ${exchanged}
+ oldValues: ${oldValues}`;
+ };
+
+ // Only one of the invocations should have succeeded to exchange - or none if spurious failures occured
+ const noExchanges = exchanged.every((v) => v === 0);
+ if (noExchanges) {
+ // Spurious failure, all values in oldValues should be the default value
+ if (!oldValues.every((v) => v === defaultValue)) {
+ t.fail(
+ `Spurious failure detected, expected only default value of ${defaultValue} in oldValues buffer.${dumpValues()}`
+ );
+ return;
+ }
+ } else {
+ // Only one invocation should have exchanged its value
+ if (exchanged.filter((v) => v === 1).length !== 1) {
+ t.fail(`More than one invocation exchanged its value.${dumpValues()}`);
+ return;
+ }
+
+ // Get its index
+ const idx = exchanged.findIndex((v) => v === 1);
+ assert(idx !== -1);
+
+ // Its output should contain the old value after exchange
+ const oldValue = pingPongValues[w % 2];
+ if (oldValues[idx] !== oldValue) {
+ t.fail(
+ `oldValues[${idx}] expected to contain old value from exchange: ${oldValue}.${dumpValues()}'`
+ );
+ return;
+ }
+
+ // The rest of oldValues should either contain the old value or the newly exchanged value,
+ // depending on whether they executed atomicCompareExchangWeak before or after invocation 'idx'.
+ const oldValuesRest = oldValues.filter((_, i) => i !== idx);
+ if (!oldValuesRest.every((v) => pingPongValues.includes(v))) {
+ t.fail(
+ `Values in oldValues buffer should be one of '${pingPongValues}', except at index '${idx} where it is '${oldValue}'.${dumpValues()}`
+ );
+ return;
+ }
+ }
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.js
new file mode 100644
index 0000000000..92717005f7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.js
@@ -0,0 +1,470 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically stores the value v in the atomic object pointed to atomic_ptr and returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+import { checkElementsEqual } from '../../../../../../util/check_contents.js';
+
+import { dispatchSizes, workgroupSizes, typedArrayCtor, kMapId } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('exchange_storage_basic').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> input : array<atomic<${scalarType}>>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ output[id] = atomicExchange(&input[id], map_id(id * 2));
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer with values [0..n]
+ const inputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(inputBuffer);
+ const data = new arrayType(inputBuffer.getMappedRange());
+ data.forEach((_, i) => data[i] = i);
+ inputBuffer.unmap();
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial input buffer as it contains
+ // values returned from atomicExchange
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ outputExpected.forEach((_, i) => outputExpected[i] = i);
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // And the input buffer should have been modified to a computed value
+ const inputExpected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ inputExpected.forEach((_, i) => inputExpected[i] = mapId.f(i * 2, numInvocations));
+ t.expectGPUBufferValuesEqual(inputBuffer, inputExpected);
+});
+
+g.test('exchange_workgroup_basic').
+specURL('https://www.w3.org/TR/WGSL/#atomic-load').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> wg_copy: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize wg[id] with this invocations global id
+ atomicStore(&wg[id], global_id);
+ workgroupBarrier();
+
+ // Test atomicExchange, storing old value into output
+ output[global_id] = atomicExchange(&wg[id], map_id(global_id * 2));
+
+ // Copy new value into wg_copy
+ wg_copy[global_id] = atomicLoad(&wg[id]);
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const wgCopyBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: wgCopyBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Output buffer should be the same as the initial wg buffer as it contains
+ // values returned from atomicExchange
+ const outputExpected = new (typedArrayCtor(t.params.scalarType))(wgNumElements * dispatchSize);
+ outputExpected.forEach((_, i) => outputExpected[i] = i);
+ t.expectGPUBufferValuesEqual(outputBuffer, outputExpected);
+
+ // And the wg copy buffer should have been modified to a computed value
+ const wgCopyBufferExpected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * dispatchSize
+ );
+ wgCopyBufferExpected.forEach(
+ (_, i) => wgCopyBufferExpected[i] = mapId.f(i * 2, numInvocations)
+ );
+ t.expectGPUBufferValuesEqual(wgCopyBuffer, wgCopyBufferExpected);
+});
+
+g.test('exchange_storage_advanced').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> input : atomic<${scalarType}>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // All invocations exchange with same single memory address, and we store
+ // the old value at the current invocation's location in the output buffer.
+ output[id] = atomicExchange(&input, map_id(id));
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer of size 1 with initial value 0
+ const inputBuffer = t.device.createBuffer({
+ size: 1 * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(inputBuffer);
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const inputBufferResult = await t.readGPUBufferRangeTyped(inputBuffer, {
+ type: arrayType,
+ typedLength: inputBuffer.size / arrayType.BYTES_PER_ELEMENT
+ });
+ const outputBufferResult = await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT
+ });
+
+ // The one value in the input buffer plus all values in the output buffer
+ // should contain initial value 0 plus map_id(0..n), unsorted.
+ const values = new arrayType([...inputBufferResult.data, ...outputBufferResult.data]);
+
+ const expected = new arrayType(values.length);
+ expected.forEach((_, i) => {
+ if (i === 0) {
+ expected[0] = 0;
+ } else {
+ expected[i] = mapId.f(i - 1, numInvocations);
+ }
+ });
+
+ // Sort both arrays and compare
+ values.sort();
+ expected.sort(); // Sort because we store hashed results when mapId == 'remap'
+ t.expectOK(checkElementsEqual(values, expected));
+});
+
+g.test('exchange_workgroup_advanced').
+specURL('https://www.w3.org/TR/WGSL/#atomic-load').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: atomic<${scalarType}>;
+
+ // Will contain the atomicExchange result for each invocation at global index
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${numInvocations * dispatchSize}>;
+
+ // Will contain the final value in wg in wg_copy for this dispatch
+ @group(0) @binding(1)
+ var<storage, read_write> wg_copy: array<${scalarType}, ${dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${numInvocations} + local_invocation_index);
+
+ // All invocations exchange with same single memory address, and we store
+ // the old value at the current invocation's location in the output buffer.
+ output[global_id] = atomicExchange(&wg, map_id(id));
+
+ // Once all invocations have completed, the first one copies the final exchanged value
+ // to wg_copy for this dispatch (workgroup_id.x)
+ workgroupBarrier();
+ if (local_invocation_index == 0u) {
+ wg_copy[workgroup_id.x] = atomicLoad(&wg);
+ }
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: numInvocations * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const wgCopyBuffer = t.device.createBuffer({
+ size: dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: wgCopyBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back buffers
+ const outputBufferResult = await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT
+ });
+ const wgCopyBufferResult = await t.readGPUBufferRangeTyped(wgCopyBuffer, {
+ type: arrayType,
+ typedLength: wgCopyBuffer.size / arrayType.BYTES_PER_ELEMENT
+ });
+
+ // For each dispatch, the one value in wgCopyBuffer plus all values in the output buffer
+ // should contain initial value 0 plus map_id(0..n), unsorted.
+
+ // Expected values for each dispatch
+ const expected = new arrayType(numInvocations + 1);
+ expected.forEach((_, i) => {
+ if (i === 0) {
+ expected[0] = 0;
+ } else {
+ expected[i] = mapId.f(i - 1, numInvocations);
+ }
+ });
+ expected.sort(); // Sort because we store hashed results when mapId == 'remap'
+
+ // Test values for each dispatch
+ for (let d = 0; d < dispatchSize; ++d) {
+ // Get values for this dispatch
+ const dispatchOffset = d * numInvocations;
+ const values = new arrayType([
+ wgCopyBufferResult.data[d], // Last 'wg' value for this dispatch
+ ...outputBufferResult.data.subarray(dispatchOffset, dispatchOffset + numInvocations) // Rest of the returned values
+ ]);
+
+ values.sort();
+ t.expectOK(checkElementsEqual(values, expected));
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.js
new file mode 100644
index 0000000000..dca1a6b9d9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.js
@@ -0,0 +1,192 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Returns the atomically loaded the value pointed to by atomic_ptr. It does not modify the object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import { dispatchSizes, workgroupSizes, typedArrayCtor, kMapId } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('load_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-load').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+
+ const wgsl = `
+ @group(0) @binding(0)
+ var<storage, read_write> input : array<atomic<${scalarType}>>;
+
+ @group(0) @binding(1)
+ var<storage, read_write> output : array<${scalarType}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+ output[id] = atomicLoad(&input[id]);
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Create input buffer with values [map_id(0)..map_id(n)]
+ const inputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(inputBuffer);
+ const data = new arrayType(inputBuffer.getMappedRange());
+ data.forEach((_, i) => data[i] = mapId.f(i, numInvocations));
+ inputBuffer.unmap();
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Both input and output buffer should be the same now
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected.forEach((_, i) => expected[i] = mapId.f(i, numInvocations));
+ t.expectGPUBufferValuesEqual(inputBuffer, expected);
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+});
+
+g.test('load_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-load').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
+
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize wg[id] with this invocations global id (mapped)
+ atomicStore(&wg[id], map_id(global_id));
+ workgroupBarrier();
+
+ // Test atomic loading of value at wg[id] and store result in output[global_id]
+ output[global_id] = atomicLoad(&wg[id]);
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Expected values should be map_id(0..n)
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ expected.forEach((_, i) => expected[i] = mapId.f(i, numInvocations));
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.js
new file mode 100644
index 0000000000..ae058e909c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, max and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by taking the max with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('max_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicMax(&output[0], id)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected[0] = numInvocations - 1;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected
+ });
+});
+
+g.test('max_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicMax(&wg[0], id)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ ).fill(initValue);
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = t.params.workgroupSize - 1;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.js
new file mode 100644
index 0000000000..7d6052c2b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.js
@@ -0,0 +1,100 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, min and store value.
+
+* Load the original value pointed to by atomic_ptr.
+ * Obtains a new value by take the min with the value v.
+ * Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('min_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = t.params.scalarType === 'u32' ? 0xffffffff : 0x7fffffff;
+ const op = `atomicMin(&output[0], id)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements).fill(initValue);
+ expected[0] = 0;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected
+ });
+});
+
+g.test('min_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = t.params.scalarType === 'u32' ? 0xffffffff : 0x7fffffff;
+ const op = `atomicMin(&wg[0], id)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ ).fill(initValue);
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = 0;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.js
new file mode 100644
index 0000000000..32c92d21cc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.js
@@ -0,0 +1,131 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, or and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by or'ing with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ kMapId,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('or_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+
+ // Allocate an output buffer with bitsize of max invocations plus 1 for validation
+ const bufferNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits low, then using atomicOr to set mapped global id bit on.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicOr(&output[i / 32], ${scalarType}(1) << i)
+ `;
+ const expected = new (typedArrayCtor(scalarType))(bufferNumElements);
+ for (let id = 0; id < numInvocations; ++id) {
+ const i = mapId.f(id, numInvocations);
+ expected[Math.floor(i / 32)] |= 1 << i;
+ }
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+});
+
+g.test('or_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize;
+
+ // Allocate workgroup array with bitsize of max invocations plus 1 for validation
+ const wgNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits low, then using atomicOr to set mapped local id bit on.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicOr(&wg[i / 32], ${scalarType}(1) << i)
+ `;
+ const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize);
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ for (let id = 0; id < numInvocations; ++id) {
+ const wg = expected.subarray(d * wgNumElements);
+ const i = mapId.f(id, numInvocations);
+ wg[Math.floor(i / 32)] |= 1 << i;
+ }
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.js
new file mode 100644
index 0000000000..1b9b7f70bd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.js
@@ -0,0 +1,301 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically stores the value v in the atomic object pointed to by atomic_ptr.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor,
+ kMapId } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('store_storage_basic').
+specURL('https://www.w3.org/TR/WGSL/#atomic-store').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const bufferNumElements = numInvocations;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const initValue = 0;
+ const op = `atomicStore(&output[id], map_id(id))`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected.forEach((_, i) => expected[i] = mapId.f(i, numInvocations));
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+});
+
+g.test('store_workgroup_basic').
+specURL('https://www.w3.org/TR/WGSL/#atomic-store').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize;
+ const wgNumElements = numInvocations;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const initValue = 0;
+ const op = `atomicStore(&wg[id], map_id(global_id))`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ expected.forEach((_, i) => expected[i] = mapId.f(i, numInvocations));
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+});
+
+g.test('store_storage_advanced').
+specURL('https://www.w3.org/TR/WGSL/#atomic-store').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+
+Tests that multiple invocations of atomicStore to the same location returns
+one of the values written.
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ @group(0) @binding(0)
+ var<storage, read_write> output : array<atomic<${scalarType}>>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+
+ // All invocations store to the same location
+ atomicStore(&output[0], map_id(id));
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ // Output buffer has only 1 element
+ const outputBuffer = t.device.createBuffer({
+ size: 1 * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(t.params.dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back the buffer
+ const outputBufferResult = (
+ await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT
+ })).
+ data;
+
+ // All invocations wrote to the output[0], so validate that it contains one
+ // of the possible computed values.
+ const expected_one_of = new arrayType(numInvocations);
+ expected_one_of.forEach((_, i) => expected_one_of[i] = mapId.f(i, numInvocations));
+
+ if (!expected_one_of.includes(outputBufferResult[0])) {
+ t.fail(
+ `Unexpected value in output[0]: '${outputBufferResult[0]}, expected value to be one of: ${expected_one_of}`
+ );
+ }
+});
+
+g.test('store_workgroup_advanced').
+specURL('https://www.w3.org/TR/WGSL/#atomic-store').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
+
+Tests that multiple invocations of atomicStore to the same location returns
+one of the values written.
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn(async (t) => {
+ const numInvocations = t.params.workgroupSize;
+ const scalarType = t.params.scalarType;
+ const dispatchSize = t.params.dispatchSize;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations, t.params.scalarType); // Defines map_id()
+
+ const wgsl =
+ `
+ var<workgroup> wg: atomic<${scalarType}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${dispatchSize}>;
+
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+
+ // All invocations of a given dispatch store to the same location.
+ // In the end, the final value should be randomly equal to one of the ids.
+ atomicStore(&wg, map_id(id));
+
+ // Once all invocations have completed, the first one copies the result
+ // to output for this dispatch (workgroup_id.x)
+ workgroupBarrier();
+ if (local_invocation_index == 0u) {
+ output[workgroup_id.x] = atomicLoad(&wg);
+ }
+ }
+ ` + extra;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const arrayType = typedArrayCtor(scalarType);
+
+ const outputBuffer = t.device.createBuffer({
+ size: dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Read back the buffer
+ const outputBufferResult = (
+ await t.readGPUBufferRangeTyped(outputBuffer, {
+ type: arrayType,
+ typedLength: outputBuffer.size / arrayType.BYTES_PER_ELEMENT
+ })).
+ data;
+
+ // Each dispatch wrote to a single atomic workgroup var that was copied
+ // to outputBuffer[dispatch]. Validate that each value in the output buffer
+ // is one of the possible computed values.
+ const expected_one_of = new arrayType(numInvocations);
+ expected_one_of.forEach((_, i) => expected_one_of[i] = mapId.f(i, numInvocations));
+
+ for (let d = 0; d < dispatchSize; d++) {
+ if (!expected_one_of.includes(outputBufferResult[d])) {
+ t.fail(
+ `Unexpected value in output[d] for dispatch d '${d}': '${outputBufferResult[d]}', expected value to be one of: ${expected_one_of}`
+ );
+ }
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.js
new file mode 100644
index 0000000000..47b1ff8ec5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, subtract and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by subtracting with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sub_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+ // Allocate one extra element to ensure it doesn't get modified
+ const bufferNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicSub(&output[0], 1)`;
+ const expected = new (typedArrayCtor(t.params.scalarType))(bufferNumElements);
+ expected[0] = -1 * numInvocations;
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected
+ });
+});
+
+g.test('sub_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ // Allocate one extra element to ensure it doesn't get modified
+ const wgNumElements = 2;
+
+ const initValue = 0;
+ const op = `atomicSub(&wg[0], 1)`;
+
+ const expected = new (typedArrayCtor(t.params.scalarType))(
+ wgNumElements * t.params.dispatchSize
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ const wg = expected.subarray(d * wgNumElements);
+ wg[0] = -1 * t.params.workgroupSize;
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.js
new file mode 100644
index 0000000000..88d96211b8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.js
@@ -0,0 +1,135 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Atomically read, xor and store value.
+
+* Load the original value pointed to by atomic_ptr.
+* Obtains a new value by xor'ing with the value v.
+* Store the new value using atomic_ptr.
+
+Returns the original value stored in the atomic object.
+`;import { makeTestGroup } from '../../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../../../gpu_test.js';
+
+import {
+ dispatchSizes,
+ workgroupSizes,
+ runStorageVariableTest,
+ runWorkgroupVariableTest,
+ kMapId,
+ typedArrayCtor } from
+'./harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('xor_storage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
+
+ // Allocate an output buffer with bitsize of max invocations plus 1 for validation
+ const bufferNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits set to some random value for each u32 in the buffer, then atomicXor each mapped global id bit.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0b11000011010110100000111100111100;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicXor(&output[i / 32], ${scalarType}(1) << i)
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(bufferNumElements).fill(initValue);
+ for (let id = 0; id < numInvocations; ++id) {
+ const i = mapId.f(id, numInvocations);
+ expected[Math.floor(i / 32)] ^= 1 << i;
+ }
+
+ runStorageVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ bufferNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+});
+
+g.test('xor_workgroup').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+AS is storage or workgroup
+T is i32 or u32
+
+fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
+`
+).
+params((u) =>
+u.
+combine('workgroupSize', workgroupSizes).
+combine('dispatchSize', dispatchSizes).
+combine('mapId', keysOf(kMapId)).
+combine('scalarType', ['u32', 'i32'])
+).
+fn((t) => {
+ const numInvocations = t.params.workgroupSize;
+
+ // Allocate workgroup array with bitsize of max invocations plus 1 for validation
+ const wgNumElements = Math.max(1, numInvocations / 32) + 1;
+
+ // Start with all bits set to some random value for each u32 in the buffer, then atomicXor each mapped global id bit.
+ // Note: Both WGSL and JS will shift left 1 by id modulo 32.
+ const initValue = 0b11000011010110100000111100111100;
+
+ const scalarType = t.params.scalarType;
+ const mapId = kMapId[t.params.mapId];
+ const extra = mapId.wgsl(numInvocations); // Defines map_id()
+ const op = `
+ let i = map_id(u32(id));
+ atomicXor(&wg[i / 32], ${scalarType}(1) << i)
+ `;
+
+ const expected = new (typedArrayCtor(scalarType))(wgNumElements * t.params.dispatchSize).fill(
+ initValue
+ );
+ for (let d = 0; d < t.params.dispatchSize; ++d) {
+ for (let id = 0; id < numInvocations; ++id) {
+ const wg = expected.subarray(d * wgNumElements);
+ const i = mapId.f(id, numInvocations);
+ wg[Math.floor(i / 32)] ^= 1 << i;
+ }
+ }
+
+ runWorkgroupVariableTest({
+ t,
+ workgroupSize: t.params.workgroupSize,
+ dispatchSize: t.params.dispatchSize,
+ wgNumElements,
+ initValue,
+ op,
+ expected,
+ extra
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/harness.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/harness.js
new file mode 100644
index 0000000000..986814d425
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/atomics/harness.js
@@ -0,0 +1,208 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from
+
+'../../../../../../../common/util/util.js';
+
+
+// Use these in combination.
+export const workgroupSizes = [1, 2, 32, 64];
+export const dispatchSizes = [1, 4, 8, 16];
+
+// Use this alone - dispatch size should be 1.
+export const onlyWorkgroupSizes = [1, 2, 4, 8, 16, 32, 64, 128, 256];
+
+export const kMapId = {
+ passthrough: {
+ f: (id, _max) => id,
+ wgsl: (_max, scalarType = 'u32') =>
+ `fn map_id(id: ${scalarType}) -> ${scalarType} { return id; }`
+ },
+ remap: {
+ f: (id, max) => ((id >>> 0) * 14957 ^ (id >>> 0) * 26561 >> 2) % max,
+ wgsl: (max, scalarType = 'u32') =>
+ `fn map_id(id: ${scalarType}) -> ${scalarType} { return ((id * 14957) ^ ((id * 26561) >> 2)) % ${max}; }`
+ }
+};
+
+export function typedArrayCtor(scalarType) {
+ switch (scalarType) {
+ case 'u32':
+ return Uint32Array;
+ case 'i32':
+ return Int32Array;
+ default:
+ assert(false, 'Atomic variables can only by u32 or i32');
+ return Uint8Array;
+ }
+}
+
+export function runStorageVariableTest({
+ t,
+ workgroupSize, // Workgroup X-size
+ dispatchSize, // Dispatch X-size
+ bufferNumElements, // Number of 32-bit elements in output buffer
+ initValue, // 32-bit initial value used to fill output buffer
+ // Atomic op source executed by the compute shader, NOTE: 'id' is global_invocation_id.x,
+ // and `output` is a storage array of atomics.
+ op,
+ expected, // Expected values array to compare against output buffer
+ extra // Optional extra WGSL source
+
+
+
+
+
+
+
+
+
+}) {
+ assert(expected.length === bufferNumElements, "'expected' buffer size is incorrect");
+
+ const scalarType = expected instanceof Uint32Array ? 'u32' : 'i32';
+ const arrayType = typedArrayCtor(scalarType);
+
+ const wgsl = `
+ @group(0) @binding(0)
+ var<storage, read_write> output : array<atomic<${scalarType}>>;
+
+ @compute @workgroup_size(${workgroupSize})
+ fn main(
+ @builtin(global_invocation_id) global_invocation_id : vec3<u32>,
+ ) {
+ let id = ${scalarType}(global_invocation_id[0]);
+ ${op};
+ }
+ ${extra || ''}
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ mappedAtCreation: true
+ });
+ // Fill with initial value
+ t.trackForCleanup(outputBuffer);
+ const data = new arrayType(outputBuffer.getMappedRange());
+ data.fill(initValue);
+ outputBuffer.unmap();
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+export function runWorkgroupVariableTest({
+ t,
+ workgroupSize, // Workgroup X-size
+ dispatchSize, // Dispatch X-size
+ wgNumElements, // Number of 32-bit elements in 'wg' array. Output buffer is sized to wgNumElements * dispatchSize.
+ initValue, // 32-bit initial value used to fill 'wg' array
+ // Atomic op source executed by the compute shader, NOTE: 'id' is local_invocation_index,
+ // `wg` is a workgroup array of atomics of size `workgroupSize`, `output` is a storage array of non-atomics of size
+ // `workgroupSize * dispatcSize` to which each dispatch of `wg` gets copied to (dispatch 0 to first workgroupSize elements,
+ // dispatch 1 to second workgroupSize elements, etc.).
+ op,
+ expected, // Expected values array to compare against output buffer
+ extra // Optional extra WGSL source
+
+
+
+
+
+
+
+
+
+}) {
+ assert(expected.length === wgNumElements * dispatchSize, "'expected' buffer size is incorrect");
+
+ const scalarType = expected instanceof Uint32Array ? 'u32' : 'i32';
+ const arrayType = typedArrayCtor(scalarType);
+
+ const wgsl = `
+ var<workgroup> wg: array<atomic<${scalarType}>, ${wgNumElements}>;
+
+ // Result of each workgroup is written to output[workgroup_id.x]
+ @group(0) @binding(0)
+ var<storage, read_write> output: array<${scalarType}, ${wgNumElements * dispatchSize}>;
+
+ @compute @workgroup_size(${workgroupSize})
+ fn main(
+ @builtin(local_invocation_index) local_invocation_index: u32,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>
+ ) {
+ let id = ${scalarType}(local_invocation_index);
+ let global_id = ${scalarType}(workgroup_id.x * ${wgNumElements} + local_invocation_index);
+
+ // Initialize workgroup array
+ if (local_invocation_index == 0) {
+ for (var i = 0u; i < ${wgNumElements}; i++) {
+ atomicStore(&wg[i], bitcast<${scalarType}>(${initValue}u));
+ }
+ }
+ workgroupBarrier();
+
+ ${op};
+
+ // Copy results to output buffer
+ workgroupBarrier();
+ if (local_invocation_index == 0) {
+ for (var i = 0u; i < ${wgNumElements}; i++) {
+ output[(workgroup_id.x * ${wgNumElements}) + i] = atomicLoad(&wg[i]);
+ }
+ }
+ }
+ ${extra || ''}
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ const outputBuffer = t.device.createBuffer({
+ size: wgNumElements * dispatchSize * arrayType.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(dispatchSize);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/bitcast.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/bitcast.spec.js
new file mode 100644
index 0000000000..d01c43dbfb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/bitcast.spec.js
@@ -0,0 +1,1275 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'bitcast' builtin function
+
+@const @must_use fn bitcast<T>(e: T ) -> T
+T is concrete numeric scalar or concerete numeric vector
+Identity function.
+
+@const @must_use fn bitcast<T>(e: S ) -> T
+@const @must_use fn bitcast<vecN<T>>(e: vecN<S> ) -> vecN<T>
+S is i32, u32, f32
+T is i32, u32, f32, and T is not S
+Reinterpretation of bits. Beware non-normal f32 values.
+
+@const @must_use fn bitcast<T>(e: vec2<f16> ) -> T
+@const @must_use fn bitcast<vec2<T>>(e: vec4<f16> ) -> vec2<T>
+@const @must_use fn bitcast<vec2<f16>>(e: T ) -> vec2<f16>
+@const @must_use fn bitcast<vec4<f16>>(e: vec2<T> ) -> vec4<f16>
+T is i32, u32, f32
+`;
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { alwaysPass, anyOf } from '../../../../../util/compare.js';
+import { kBit, kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ i32,
+ u32,
+ f16,
+ TypeF32,
+ TypeI32,
+ TypeU32,
+ TypeF16,
+ TypeVec,
+ Vector,
+
+ toVector } from
+'../../../../../util/conversion.js';
+import { FPInterval, FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullI32Range,
+ fullU32Range,
+ fullF16Range,
+ linearRange,
+ isSubnormalNumberF32,
+ isSubnormalNumberF16,
+ cartesianProduct,
+ isFiniteF32,
+ isFiniteF16 } from
+'../../../../../util/math.js';
+import {
+ reinterpretI32AsF32,
+ reinterpretI32AsU32,
+ reinterpretF32AsI32,
+ reinterpretF32AsU32,
+ reinterpretU32AsF32,
+ reinterpretU32AsI32,
+ reinterpretU16AsF16,
+ reinterpretF16AsU16 } from
+'../../../../../util/reinterpret.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtinWithPredeclaration } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const numNaNs = 11;
+const f32InfAndNaNInU32 = [
+// 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 = 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 = [...fullF32Range(), kValue.f32.negative.zero];
+const f32RangeWithInfAndNaN = [...f32FiniteRange, ...f32InfAndNaNInF32];
+
+// F16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
+const f16FiniteInF16 = [...fullF16Range(), kValue.f16.negative.zero];
+const f16FiniteInU16 = f16FiniteInF16.map((u) => reinterpretF16AsU16(u));
+
+const f16InfAndNaNInU16 = [
+// 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 = 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) {
+ 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) {
+ // 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) {
+ 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) {
+ 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) {
+ 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, len) {
+ const result = [];
+ for (let i = 0; i < input.length; i++) {
+ const sub = [];
+ 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 = [
+...fullU32Range(),
+...f16Vec2ZerosInU32,
+...f16Vec2InfAndNaNInU32];
+
+// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const u32RangeForF16Vec2Finite = u32RangeForF16Vec2FiniteInfNaN.filter(
+ canU32BitcastToFiniteVec2F16
+);
+// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const i32RangeForF16Vec2FiniteInfNaN = [
+...fullI32Range(),
+...f16Vec2ZerosInI32,
+...f16Vec2InfAndNaNInI32];
+
+// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const i32RangeForF16Vec2Finite = 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 = [
+...f32RangeWithInfAndNaN,
+...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32)];
+
+// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const f32FiniteRangeForF16Vec2Finite = 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 = [
+...fullI32Range(),
+...f32ZerosInI32,
+...f32InfAndNaNInI32];
+
+// i32 cases for bitcasting to f32 finite only.
+const i32RangeForF32Finite = i32RangeForF32FiniteInfNaN.filter((i) =>
+isFiniteF32(reinterpretI32AsF32(i))
+);
+// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const u32RangeForF32FiniteInfNaN = [
+...fullU32Range(),
+...f32ZerosInU32,
+...f32InfAndNaNInU32];
+
+// u32 cases for bitcasting to f32 finite only.
+const u32RangeForF32Finite = 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) {
+ if (!isFiniteF32(f)) return anyF32;
+ const acceptable = [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) {
+ if (!isFiniteF32(f)) return anyU32;
+ const acceptable = [
+ 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) {
+ if (!isFiniteF32(f)) return anyI32;
+ const acceptable = [
+ 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) {
+ const f = 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 = [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) {
+ const f = 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 = [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) {
+ // 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) {
+ 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) {
+ 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) {
+ 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) {
+ // 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) {
+ 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) {
+ 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) {
+ 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.
+
+
+
+
+
+
+
+/**
+ * @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) {
+ 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,
+type)
+{
+ assert(f16x2InU16x2.length === 2);
+ let reinterpretFromU32;
+ let expectationsForValue;
+ let unboundedExpectations;
+ if (type === 'u32') {
+ reinterpretFromU32 = (x) => 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) => 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) => reinterpretU32AsF32(x);
+ expectationsForValue = (x) => {
+ // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result.
+ if (!isFiniteF32(x)) {
+ return [f32UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (x === 0.0) {
+ return [f32ZerosInterval];
+ }
+ const exactInterval = FP.f32.toInterval(x);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])];
+ };
+ unboundedExpectations = [f32UnboundedInterval];
+ }
+ // Return unbounded expection if f16 Inf/NaN occurs
+ if (
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) ||
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1])))
+ {
+ return { possibleExpectations: unboundedExpectations, isUnbounded: true };
+ }
+ const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16);
+ const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap(
+ (possibleBitsU16x2) => {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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)
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 i32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4) {
+ 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)
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 f32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4) {
+ 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],
+ e[1]]
+ )
+ );
+}
+
+export const d = makeCaseCache('bitcast', {
+ // Identity Cases
+ i32_to_i32: () => fullI32Range().map((e) => ({ input: i32(e), expected: i32(e) })),
+ u32_to_u32: () => fullU32Range().map((e) => ({ input: u32(e), expected: u32(e) })),
+ f32_inf_nan_to_f32: () =>
+ f32RangeWithInfAndNaN.map((e) => ({
+ input: f32(e),
+ expected: bitcastF32ToF32Comparator(e)
+ })),
+ f32_to_f32: () =>
+ f32FiniteRange.map((e) => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })),
+ f16_inf_nan_to_f16: () =>
+ [...f16FiniteInF16, ...f16InfAndNaNInF16].map((e) => ({
+ input: f16(e),
+ expected: bitcastF16ToF16Comparator(e)
+ })),
+ f16_to_f16: () =>
+ f16FiniteInF16.map((e) => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })),
+
+ // i32,u32,f32 to different i32,u32,f32
+ i32_to_u32: () => fullI32Range().map((e) => ({ input: i32(e), expected: u32(e) })),
+ i32_to_f32: () =>
+ i32RangeForF32Finite.map((e) => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e)
+ })),
+ i32_to_f32_inf_nan: () =>
+ i32RangeForF32FiniteInfNaN.map((e) => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e)
+ })),
+ u32_to_i32: () => fullU32Range().map((e) => ({ input: u32(e), expected: i32(e) })),
+ u32_to_f32: () =>
+ u32RangeForF32Finite.map((e) => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e)
+ })),
+ u32_to_f32_inf_nan: () =>
+ u32RangeForF32FiniteInfNaN.map((e) => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e)
+ })),
+ f32_inf_nan_to_i32: () =>
+ f32RangeWithInfAndNaN.map((e) => ({
+ input: f32(e),
+ expected: bitcastF32ToI32Comparator(e)
+ })),
+ f32_to_i32: () =>
+ f32FiniteRange.map((e) => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })),
+
+ f32_inf_nan_to_u32: () =>
+ f32RangeWithInfAndNaN.map((e) => ({
+ input: f32(e),
+ expected: bitcastF32ToU32Comparator(e)
+ })),
+ f32_to_u32: () =>
+ f32FiniteRange.map((e) => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })),
+
+ // i32,u32,f32 to vec2<f16>
+ u32_to_vec2_f16_inf_nan: () =>
+ u32RangeForF16Vec2FiniteInfNaN.map((e) => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e)
+ })),
+ u32_to_vec2_f16: () =>
+ u32RangeForF16Vec2Finite.map((e) => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e)
+ })),
+ i32_to_vec2_f16_inf_nan: () =>
+ i32RangeForF16Vec2FiniteInfNaN.map((e) => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e)
+ })),
+ i32_to_vec2_f16: () =>
+ i32RangeForF16Vec2Finite.map((e) => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e)
+ })),
+ f32_inf_nan_to_vec2_f16_inf_nan: () =>
+ f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map((e) => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e)
+ })),
+ f32_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map((e) => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e)
+ })),
+
+ // vec2<i32>, vec2<u32>, vec2<f32> to vec4<f16>
+ vec2_i32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map((e) => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e)
+ })),
+ vec2_i32_to_vec4_f16: () =>
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map((e) => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e)
+ })),
+ vec2_u32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map((e) => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e)
+ })),
+ vec2_u32_to_vec4_f16: () =>
+ slidingSlice(u32RangeForF16Vec2Finite, 2).map((e) => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e)
+ })),
+ vec2_f32_inf_nan_to_vec4_f16_inf_nan: () =>
+ slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map((e) => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e)
+ })),
+ vec2_f32_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map((e) => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e)
+ })),
+
+ // vec2<f16> to i32, u32, f32
+ vec2_f16_to_u32: () =>
+ f16Vec2FiniteInU16x2.map((e) => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e)
+ })),
+ vec2_f16_inf_nan_to_u32: () =>
+ f16Vec2FiniteInfNanInU16x2.map((e) => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e)
+ })),
+ vec2_f16_to_i32: () =>
+ f16Vec2FiniteInU16x2.map((e) => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e)
+ })),
+ vec2_f16_inf_nan_to_i32: () =>
+ f16Vec2FiniteInfNanInU16x2.map((e) => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e)
+ })),
+ vec2_f16_to_f32_finite: () =>
+ f16Vec2FiniteInU16x2.
+ filter((u16x2) => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2)))).
+ map((e) => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e)
+ })),
+ vec2_f16_inf_nan_to_f32: () =>
+ f16Vec2FiniteInfNanInU16x2.map((e) => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e)
+ })),
+
+ // vec4<f16> to vec2 of i32, u32, f32
+ vec4_f16_to_vec2_u32: () =>
+ f16Vec2FiniteInU16x4.map((e) => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e)
+ })),
+ vec4_f16_inf_nan_to_vec2_u32: () =>
+ f16Vec2FiniteInfNanInU16x4.map((e) => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e)
+ })),
+ vec4_f16_to_vec2_i32: () =>
+ f16Vec2FiniteInU16x4.map((e) => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e)
+ })),
+ vec4_f16_inf_nan_to_vec2_i32: () =>
+ f16Vec2FiniteInfNanInU16x4.map((e) => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e)
+ })),
+ vec4_f16_to_vec2_f32_finite: () =>
+ f16Vec2FiniteInU16x4.
+ filter(
+ (u16x4) =>
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) &&
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4))))
+ ).
+ map((e) => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e)
+ })),
+ vec4_f16_inf_nan_to_vec2_f32: () =>
+ f16Vec2FiniteInfNanInU16x4.map((e) => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e)
+ }))
+});
+
+/**
+ * @returns a ShaderBuilder that generates a call to bitcast,
+ * using appropriate destination type, which optionally can be
+ * a WGSL type alias.
+ */
+function bitcastBuilder(canonicalDestType, params) {
+ const destType = params.vectorize ?
+ `vec${params.vectorize}<${canonicalDestType}>` :
+ canonicalDestType;
+
+ return builtinWithPredeclaration(
+ `bitcast<${destType}>`,
+ params.alias ? `alias myalias = ${destType};` : ''
+ );
+}
+
+// Identity cases
+g.test('i32_to_i32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast i32 to i32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get('i32_to_i32');
+ await run(t, bitcastBuilder('i32', t.params), [TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('u32_to_u32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast u32 to u32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get('u32_to_u32');
+ await run(t, bitcastBuilder('u32', t.params), [TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('f32_to_f32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast f32 to f32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_f32' : 'f32_inf_nan_to_f32'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeF32], TypeF32, t.params, cases);
+});
+
+// To i32 from u32, f32
+g.test('u32_to_i32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast u32 to i32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get('u32_to_i32');
+ await run(t, bitcastBuilder('i32', t.params), [TypeU32], TypeI32, t.params, cases);
+});
+
+g.test('f32_to_i32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast f32 to i32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_i32' : 'f32_inf_nan_to_i32'
+ );
+ await run(t, bitcastBuilder('i32', t.params), [TypeF32], TypeI32, t.params, cases);
+});
+
+// To u32 from i32, f32
+g.test('i32_to_u32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast i32 to u32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get('i32_to_u32');
+ await run(t, bitcastBuilder('u32', t.params), [TypeI32], TypeU32, t.params, cases);
+});
+
+g.test('f32_to_u32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast f32 to i32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_u32' : 'f32_inf_nan_to_u32'
+ );
+ await run(t, bitcastBuilder('u32', t.params), [TypeF32], TypeU32, t.params, cases);
+});
+
+// To f32 from i32, u32
+g.test('i32_to_f32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast i32 to f32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'i32_to_f32' : 'i32_to_f32_inf_nan'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeI32], TypeF32, t.params, cases);
+});
+
+g.test('u32_to_f32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast u32 to f32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'u32_to_f32' : 'u32_to_f32_inf_nan'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeU32], TypeF32, t.params, cases);
+});
+
+// 16 bit types
+
+// f16 cases
+
+// f16: Identity
+g.test('f16_to_f16').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast f16 to f16 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('vectorize', [undefined, 2, 3, 4]).
+combine('alias', [false, true])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f16_to_f16' : 'f16_inf_nan_to_f16'
+ );
+ await run(t, bitcastBuilder('f16', t.params), [TypeF16], TypeF16, t.params, cases);
+});
+
+// f16: 32-bit scalar numeric to vec2<f16>
+g.test('i32_to_vec2h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast i32 to vec2h tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'i32_to_vec2_f16' : 'i32_to_vec2_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [TypeI32],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('u32_to_vec2h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast u32 to vec2h tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'u32_to_vec2_f16' : 'u32_to_vec2_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [TypeU32],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_to_vec2h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast u32 to vec2h tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'f32_to_vec2_f16' : 'f32_inf_nan_to_vec2_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [TypeF32],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+// f16: vec2<32-bit scalar numeric> to vec4<f16>
+g.test('vec2i_to_vec4h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec2i to vec4h tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_i32_to_vec4_f16' : 'vec2_i32_to_vec4_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [TypeVec(2, TypeI32)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vec2u_to_vec4h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec2u to vec4h tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_u32_to_vec4_f16' : 'vec2_u32_to_vec4_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [TypeVec(2, TypeU32)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('vec2f_to_vec4h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec2f to vec2h tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ?
+ 'vec2_f32_to_vec4_f16' :
+ 'vec2_f32_inf_nan_to_vec4_f16_inf_nan'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [TypeVec(2, TypeF32)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+// f16: vec2<f16> to 32-bit scalar numeric
+g.test('vec2h_to_i32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec2h to i32 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_f16_to_i32' : 'vec2_f16_inf_nan_to_i32'
+ );
+ await run(t, bitcastBuilder('i32', t.params), [TypeVec(2, TypeF16)], TypeI32, t.params, cases);
+});
+
+g.test('vec2h_to_u32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec2h to u32 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_f16_to_u32' : 'vec2_f16_inf_nan_to_u32'
+ );
+ await run(t, bitcastBuilder('u32', t.params), [TypeVec(2, TypeF16)], TypeU32, t.params, cases);
+});
+
+g.test('vec2h_to_f32').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec2h to f32 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec2_f16_to_f32_finite' : 'vec2_f16_inf_nan_to_f32'
+ );
+ await run(t, bitcastBuilder('f32', t.params), [TypeVec(2, TypeF16)], TypeF32, t.params, cases);
+});
+
+// f16: vec4<f16> to vec2<32-bit scalar numeric>
+g.test('vec4h_to_vec2i').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec4h to vec2i tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_i32' : 'vec4_f16_inf_nan_to_vec2_i32'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<i32>', t.params),
+ [TypeVec(4, TypeF16)],
+ TypeVec(2, TypeI32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vec4h_to_vec2u').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec4h to vec2u tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_u32' : 'vec4_f16_inf_nan_to_vec2_u32'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<u32>', t.params),
+ [TypeVec(4, TypeF16)],
+ TypeVec(2, TypeU32),
+ t.params,
+ cases
+ );
+});
+
+g.test('vec4h_to_vec2f').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`bitcast vec4h to vec2f tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('alias', [false, true])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ // Infinities and NaNs are errors in const-eval.
+ t.params.inputSource === 'const' ?
+ 'vec4_f16_to_vec2_f32_finite' :
+ 'vec4_f16_inf_nan_to_vec2_f32'
+ );
+ await run(
+ t,
+ bitcastBuilder('vec2<f32>', t.params),
+ [TypeVec(4, TypeF16)],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/builtin.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/builtin.js
new file mode 100644
index 0000000000..cd3e52298c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/builtin.js
@@ -0,0 +1,24 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { abstractFloatShaderBuilder, basicExpressionBuilder,
+ basicExpressionWithPredeclarationBuilder } from
+
+'../../expression.js';
+
+/* @returns a ShaderBuilder that calls the builtin with the given name */
+export function builtin(name) {
+ return basicExpressionBuilder((values) => `${name}(${values.join(', ')})`);
+}
+
+/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractFloats */
+export function abstractBuiltin(name) {
+ return abstractFloatShaderBuilder((values) => `${name}(${values.join(', ')})`);
+}
+
+/* @returns a ShaderBuilder that calls the builtin with the given name and has given predeclaration */
+export function builtinWithPredeclaration(name, predeclaration) {
+ return basicExpressionWithPredeclarationBuilder(
+ (values) => `${name}(${values.join(', ')})`,
+ predeclaration
+ );
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ceil.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ceil.spec.js
new file mode 100644
index 0000000000..076c7178ef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ceil.spec.js
@@ -0,0 +1,101 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'ceil' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn ceil(e: T ) -> T
+Returns the ceiling of e. Component-wise when T is a vector.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('ceil', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Small positive numbers
+ 0.1,
+ 0.9,
+ 1.0,
+ 1.1,
+ 1.9,
+ // Small negative numbers
+ -0.1,
+ -0.9,
+ -1.0,
+ -1.1,
+ -1.9,
+ 0x80000000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.ceilInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Small positive numbers
+ 0.1,
+ 0.9,
+ 1.0,
+ 1.1,
+ 1.9,
+ // Small negative numbers
+ -0.1,
+ -0.9,
+ -1.0,
+ -1.1,
+ -1.9,
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.ceilInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('ceil'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('ceil'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/clamp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/clamp.spec.js
new file mode 100644
index 0000000000..6d4d05ba36
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/clamp.spec.js
@@ -0,0 +1,195 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'clamp' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn clamp(e: T , low: T, high: T) -> T
+Returns min(max(e,low),high). Component-wise when T is a vector.
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const clamp(e: T , low: T , high: T) -> T
+Returns either min(max(e,low),high), or the median of the three values e, low, high.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+
+ 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, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max];
+
+const i32Values = [
+kValue.i32.negative.min,
+-3,
+-2,
+-1,
+0,
+1,
+2,
+3,
+0x70000000,
+kValue.i32.positive.max];
+
+
+export const d = makeCaseCache('clamp', {
+ u32_non_const: () => {
+ return generateIntegerTestCases(u32Values, TypeU32, 'non-const');
+ },
+ u32_const: () => {
+ return generateIntegerTestCases(u32Values, TypeU32, 'const');
+ },
+ i32_non_const: () => {
+ return generateIntegerTestCases(i32Values, TypeI32, 'non-const');
+ },
+ i32_const: () => {
+ return generateIntegerTestCases(i32Values, TypeI32, 'const');
+ },
+ f32_const: () => {
+ return generateFloatTestCases(sparseF32Range(), 'f32', 'const');
+ },
+ f32_non_const: () => {
+ return generateFloatTestCases(sparseF32Range(), 'f32', 'non-const');
+ },
+ f16_const: () => {
+ return generateFloatTestCases(sparseF16Range(), 'f16', 'const');
+ },
+ f16_non_const: () => {
+ return generateFloatTestCases(sparseF16Range(), 'f16', 'non-const');
+ },
+ abstract: () => {
+ return generateFloatTestCases(sparseF64Range(), 'abstract', 'const');
+ }
+});
+
+/** @returns a set of clamp test cases from an ascending list of integer values */
+function generateIntegerTestCases(
+test_values,
+type,
+stage)
+{
+ 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,
+trait,
+stage)
+{
+ 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])
+).
+unimplemented();
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('clamp'), [TypeU32, TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const');
+ await run(t, builtin('clamp'), [TypeI32, TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('clamp'),
+ [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('clamp'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('clamp'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cos.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cos.spec.js
new file mode 100644
index 0000000000..46b5eade73
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cos.spec.js
@@ -0,0 +1,84 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'cos' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn cos(e: T ) -> T
+Returns the cosine of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('cos', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.cosInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.cosInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('cos'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('cos'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cosh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cosh.spec.js
new file mode 100644
index 0000000000..4304a39268
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cosh.spec.js
@@ -0,0 +1,68 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'cosh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn cosh(e: T ) -> T
+Returns the hyperbolic cosine of e. Component-wise when T is a vector
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('cosh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.coshInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.coshInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.coshInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.coshInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('cosh'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('cosh'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.js
new file mode 100644
index 0000000000..8622bef96e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.js
@@ -0,0 +1,250 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'countLeadingZeros' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn countLeadingZeros(e: T ) -> T
+The number of consecutive 0 bits starting from the most significant bit of e,
+when T is a scalar type.
+Component-wise when T is a vector.
+Also known as "clz" in some languages.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('countLeadingZeros'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(31) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(30) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(29) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(28) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(27) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(26) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(25) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(24) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(23) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(8) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(7) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(6) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(5) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(4) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(3) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(2) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(0) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32(30) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32(29) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32(28) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32(27) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32(26) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32(25) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(24) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32(23) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32(21) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32(11) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32(8) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32(7) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32(6) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32(5) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32(4) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32(3) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32(2) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32(1) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32(29) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32(28) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32(27) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32(26) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32(25) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(24) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32(23) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32(21) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32(20) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32(18) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32(17) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32(15) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32(11) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32(10) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32(9) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32(8) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32(7) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32(6) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32(5) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32(4) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32(3) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32(2) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32(1) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32(0) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('countLeadingZeros'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(31) },
+
+ // 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(30) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(29) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(28) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(27) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(26) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(25) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(24) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(23) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(8) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(7) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(6) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(5) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(4) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(3) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(2) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(0) },
+
+ // 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32(30) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32(29) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32(28) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32(27) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32(26) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32(25) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(24) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32(23) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32(21) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32(11) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32(8) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32(7) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32(6) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32(5) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32(4) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32(3) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32(2) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) },
+
+ // random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32(29) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32(28) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32(27) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32(26) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32(25) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(24) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32(23) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32(21) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32(20) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32(18) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32(17) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32(15) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32(11) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32(10) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32(9) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32(8) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32(7) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32(6) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32(5) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32(4) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32(3) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32(2) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32(1) },
+ { input: i32Bits(0b11100010011110101101101110101111), expected: i32(0) }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.js
new file mode 100644
index 0000000000..500b02ba61
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.js
@@ -0,0 +1,249 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'countOneBits' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn countOneBits(e: T ) -> T
+The number of 1 bits in the representation of e.
+Also known as "population count".
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('countOneBits'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(0) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(1) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(1) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(1) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32(21) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32(23) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32(24) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32(25) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32(27) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32(28) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32(29) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32(30) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32(31) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(32) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32(10) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32(15) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32(17) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32(20) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32(17) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32(21) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32(23) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32(21) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32(18) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32(24) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32(26) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32(21) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('countOneBits'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(0) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(1) },
+
+ // 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(1) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(1) },
+
+ // 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32(21) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32(23) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32(24) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32(25) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32(28) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32(29) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32(30) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32(31) },
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(32) },
+
+ // random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32(10) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32(15) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32(17) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32(20) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32(17) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32(21) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32(23) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32(21) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32(18) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32(24) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32(26) },
+ { input: i32Bits(0b11100010011110101101101110101111), expected: i32(21) }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.js
new file mode 100644
index 0000000000..ca3f78ee05
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.js
@@ -0,0 +1,250 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'countTrailingZeros' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn countTrailingZeros(e: T ) -> T
+The number of consecutive 0 bits starting from the least significant bit of e,
+when T is a scalar type.
+Component-wise when T is a vector.
+Also known as "ctz" in some languages.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('countTrailingZeros'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
+
+ // High bit
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) },
+
+ // 0's before trailing 1
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) },
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) },
+
+ // 1's before trailing 1
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) },
+ { input: u32Bits(0b11111111111111111111111111111110), expected: u32(1) },
+ { input: u32Bits(0b11111111111111111111111111111100), expected: u32(2) },
+ { input: u32Bits(0b11111111111111111111111111111000), expected: u32(3) },
+ { input: u32Bits(0b11111111111111111111111111110000), expected: u32(4) },
+ { input: u32Bits(0b11111111111111111111111111100000), expected: u32(5) },
+ { input: u32Bits(0b11111111111111111111111111000000), expected: u32(6) },
+ { input: u32Bits(0b11111111111111111111111110000000), expected: u32(7) },
+ { input: u32Bits(0b11111111111111111111111100000000), expected: u32(8) },
+ { input: u32Bits(0b11111111111111111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b11111111111111111111110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111111111111111100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111111111111111111000000000000), expected: u32(12) },
+ { input: u32Bits(0b11111111111111111110000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111111111111111100000000000000), expected: u32(14) },
+ { input: u32Bits(0b11111111111111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11111111111111110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b11111111111111100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11111111111111000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111111111110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b11111111111100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111111111000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b11111111110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11111111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b11111111000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11111110000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11111100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b11111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b11110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b11100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b11000000000000000000000000000000), expected: u32(30) },
+
+ // random before trailing 1
+ { input: u32Bits(0b11110000001111111101111010001111), expected: u32(0) },
+ { input: u32Bits(0b11011110111111100101110011110010), expected: u32(1) },
+ { input: u32Bits(0b11110111011011111111010000111100), expected: u32(2) },
+ { input: u32Bits(0b11010011011101111111010011101000), expected: u32(3) },
+ { input: u32Bits(0b11010111110111110001111110110000), expected: u32(4) },
+ { input: u32Bits(0b11111101111101111110101111100000), expected: u32(5) },
+ { input: u32Bits(0b11111001111011111001111011000000), expected: u32(6) },
+ { input: u32Bits(0b11001110110111110111111010000000), expected: u32(7) },
+ { input: u32Bits(0b11101111011111101110101100000000), expected: u32(8) },
+ { input: u32Bits(0b11111101111011111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b10011111011101110110110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111101101111011100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111011010110111011000000000000), expected: u32(12) },
+ { input: u32Bits(0b00111101010000111010000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111011110001101100000000000000), expected: u32(14) },
+ { input: u32Bits(0b10111111010111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11011101111010110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b01110100110110100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11100111001011000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111001110110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00110100100100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111010011000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000010110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11100111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00101101000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11011010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11010100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b10111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b01110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b10100000000000000000000000000000), expected: u32(29) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('countTrailingZeros'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
+
+ // High bit
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(31) },
+
+ // 0's before trailing 1
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) },
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) },
+
+ // 1's before trailing 1
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) },
+ { input: i32Bits(0b11111111111111111111111111111110), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111100), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111111000), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111110000), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111100000), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111111000000), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111110000000), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111100000000), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111111000000000000), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111110000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111100000000000000), expected: i32(14) },
+ { input: i32Bits(0b11111111111111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11111111111111110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b11111111111111100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11111111111111000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111111111110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b11111111111100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111111111000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b11111111110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11111111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b11111111000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11111110000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11111100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b11111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b11110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b11100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b11000000000000000000000000000000), expected: i32(30) },
+
+ // random before trailing 1
+ { input: i32Bits(0b11110000001111111101111010001111), expected: i32(0) },
+ { input: i32Bits(0b11011110111111100101110011110010), expected: i32(1) },
+ { input: i32Bits(0b11110111011011111111010000111100), expected: i32(2) },
+ { input: i32Bits(0b11010011011101111111010011101000), expected: i32(3) },
+ { input: i32Bits(0b11010111110111110001111110110000), expected: i32(4) },
+ { input: i32Bits(0b11111101111101111110101111100000), expected: i32(5) },
+ { input: i32Bits(0b11111001111011111001111011000000), expected: i32(6) },
+ { input: i32Bits(0b11001110110111110111111010000000), expected: i32(7) },
+ { input: i32Bits(0b11101111011111101110101100000000), expected: i32(8) },
+ { input: i32Bits(0b11111101111011111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b10011111011101110110110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111101101111011100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111011010110111011000000000000), expected: i32(12) },
+ { input: i32Bits(0b00111101010000111010000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111011110001101100000000000000), expected: i32(14) },
+ { input: i32Bits(0b10111111010111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11011101111010110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b01110100110110100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11100111001011000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111001110110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00110100100100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111010011000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000010110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11100111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00101101000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11011010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11010100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b10111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b01110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b10100000000000000000000000000000), expected: i32(29) }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cross.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cross.spec.js
new file mode 100644
index 0000000000..7d1c862bf1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/cross.spec.js
@@ -0,0 +1,113 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'cross' builtin function
+
+T is AbstractFloat, f32, or f16
+@const fn cross(e1: vec3<T> ,e2: vec3<T>) -> vec3<T>
+Returns the cross product of e1 and e2.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseVectorF64Range, vectorF16Range, vectorF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('cross', {
+ f32_const: () => {
+ return FP.f32.generateVectorPairToVectorCases(
+ vectorF32Range(3),
+ vectorF32Range(3),
+ 'finite',
+ FP.f32.crossInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateVectorPairToVectorCases(
+ vectorF32Range(3),
+ vectorF32Range(3),
+ 'unfiltered',
+ FP.f32.crossInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateVectorPairToVectorCases(
+ vectorF16Range(3),
+ vectorF16Range(3),
+ 'finite',
+ FP.f16.crossInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateVectorPairToVectorCases(
+ vectorF16Range(3),
+ vectorF16Range(3),
+ 'unfiltered',
+ FP.f16.crossInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateVectorPairToVectorCases(
+ sparseVectorF64Range(3),
+ sparseVectorF64Range(3),
+ 'finite',
+ FP.abstract.crossInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('cross'),
+ [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat)],
+ TypeVec(3, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(
+ t,
+ builtin('cross'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(
+ t,
+ builtin('cross'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/degrees.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/degrees.spec.js
new file mode 100644
index 0000000000..cf3a7e34bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/degrees.spec.js
@@ -0,0 +1,95 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'degrees' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<T>
+@const fn degrees(e1: T ) -> T
+Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when T is a vector
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('degrees', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.degreesInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.degreesInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.degreesInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.degreesInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'finite',
+ FP.abstract.degreesInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('degrees'),
+ [TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('degrees'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('degrees'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/determinant.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/determinant.spec.js
new file mode 100644
index 0000000000..730c5b0ca2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/determinant.spec.js
@@ -0,0 +1,137 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'determinant' builtin function
+
+T is AbstractFloat, f32, or f16
+@const determinant(e: matCxC<T> ) -> T
+Returns the determinant of e.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeMat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Accuracy for determinant is only defined for e, where e is an integer and
+// |e| < quadroot(2**21) [~38],
+// due to computational complexity of calculating the general solution for 4x4,
+// so custom matrices are used.
+//
+// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of
+// quadroot, but using the tighter 4x4 limits for all cases for simplicity.
+const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38];
+
+const kDeterminantMatrixValues = {
+ 2: kDeterminantValues.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx]]
+ ),
+ 3: kDeterminantValues.map((f, idx) => [
+ [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
+ [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
+ [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx]]
+ ),
+ 4: kDeterminantValues.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx],
+
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx],
+
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx],
+
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx]]
+
+ )
+};
+
+// Cases: f32_matDxD_[non_]const
+const f32_cases = [2, 3, 4].
+flatMap((dim) =>
+[true, false].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].
+flatMap((dim) =>
+[true, false].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])).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f32 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `f32_mat${dim}x${dim}_const` :
+ `f32_mat${dim}x${dim}_non_const`
+ );
+ await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF32)], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f16 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('dim', [2, 3, 4])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const dim = t.params.dim;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `f16_mat${dim}x${dim}_const` :
+ `f16_mat${dim}x${dim}_non_const`
+ );
+ await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF16)], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/distance.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/distance.spec.js
new file mode 100644
index 0000000000..bf33822288
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/distance.spec.js
@@ -0,0 +1,241 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'distance' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn distance(e1: T ,e2: T ) -> f32
+Returns the distance between e1 and e2 (e.g. length(e1-e2)).
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullF16Range,
+ sparseVectorF32Range,
+ sparseVectorF16Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].map((nonConst) => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorPairToIntervalCases(
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.distanceInterval
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('distance', {
+ f32_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'finite',
+ FP.f32.distanceInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.distanceInterval
+ );
+ },
+ ...f32_vec_cases,
+ f16_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'finite',
+ FP.f16.distanceInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.distanceInterval
+ );
+ },
+ ...f16_vec_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('distance'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('distance'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('distance'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dot.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dot.spec.js
new file mode 100644
index 0000000000..def9be1611
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dot.spec.js
@@ -0,0 +1,182 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dot' builtin function
+
+T is AbstractInt, AbstractFloat, i32, u32, f32, or f16
+@const fn dot(e1: vecN<T>,e2: vecN<T>) -> T
+Returns the dot product of e1 and e2.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseVectorF32Range, vectorF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: [f32|f16]_vecN_[non_]const
+const cases = ['f32', 'f16'].
+flatMap((trait) =>
+[2, 3, 4].flatMap((N) =>
+[true, false].map((nonConst) => ({
+ [`${trait}_vec${N}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ // vec3 and vec4 require calculating all possible permutations, so their runtime is much
+ // longer per test, so only using sparse vectors for them.
+ return FP[trait].generateVectorPairToIntervalCases(
+ N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
+ N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
+ nonConst ? 'unfiltered' : 'finite',
+ FP[trait].dotInterval
+ );
+ }
+}))
+)
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('dot', cases);
+
+g.test('abstract_int').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`abstract int tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`i32 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`u32 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`abstract float test`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeF32,
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('dot'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeF16,
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdx.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdx.spec.js
new file mode 100644
index 0000000000..9d595f7d23
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdx.spec.js
@@ -0,0 +1,23 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dpdx' builtin function
+
+T is f32 or vecN<f32>
+fn dpdx(e:T) -> T
+Partial derivative of e with respect to window x coordinates.
+The result is the same as either dpdxFine(e) or dpdxCoarse(e).
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.js
new file mode 100644
index 0000000000..db31f42df1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.js
@@ -0,0 +1,22 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dpdxCoarse' builtin function
+
+T is f32 or vecN<f32>
+fn dpdxCoarse(e:T) ->T
+Returns the partial derivative of e with respect to window x coordinates using local differences.
+This may result in fewer unique positions that dpdxFine(e).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.js
new file mode 100644
index 0000000000..5eaf792439
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.js
@@ -0,0 +1,21 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dpdxFine' builtin function
+
+T is f32 or vecN<f32>
+fn dpdxFine(e:T) ->T
+Returns the partial derivative of e with respect to window x coordinates.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdy.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdy.spec.js
new file mode 100644
index 0000000000..edba9b14c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdy.spec.js
@@ -0,0 +1,22 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dpdy' builtin function
+
+T is f32 or vecN<f32>
+fn dpdy(e:T) ->T
+Partial derivative of e with respect to window y coordinates.
+The result is the same as either dpdyFine(e) or dpdyCoarse(e).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.js
new file mode 100644
index 0000000000..f002a587b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.js
@@ -0,0 +1,22 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dpdyCoarse' builtin function
+
+T is f32 or vecN<f32>
+fn dpdyCoarse(e:T) ->T
+Returns the partial derivative of e with respect to window y coordinates using local differences.
+This may result in fewer unique positions that dpdyFine(e).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 test`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.js
new file mode 100644
index 0000000000..405914acf1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.js
@@ -0,0 +1,21 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'dpdyFine' builtin function
+
+T is f32 or vecN<f32>
+fn dpdyFine(e:T) ->T
+Returns the partial derivative of e with respect to window y coordinates.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp.spec.js
new file mode 100644
index 0000000000..9eac534ba3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp.spec.js
@@ -0,0 +1,90 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'exp' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn exp(e1: T ) -> T
+Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not
+// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave
+const f32_inputs = [
+0, // Returns 1 by definition
+-89, // Returns subnormal value
+kValue.f32.negative.min, // Closest to returning 0 as possible
+...biasedRange(kValue.f32.negative.max, -88, 100),
+...biasedRange(kValue.f32.positive.min, 88, 100),
+...linearRange(89, 709, 10) // Overflows f32, but not f64
+];
+
+// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not
+const f16_inputs = [
+0, // Returns 1 by definition
+-12, // Returns subnormal value
+kValue.f16.negative.min, // Closest to returning 0 as possible
+...biasedRange(kValue.f16.negative.max, -11, 100),
+...biasedRange(kValue.f16.positive.min, 11, 100),
+...linearRange(12, 709, 10) // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('exp'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('exp'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp2.spec.js
new file mode 100644
index 0000000000..288079ef82
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/exp2.spec.js
@@ -0,0 +1,90 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'exp2' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn exp2(e: T ) -> T
+Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not
+// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave
+const f32_inputs = [
+0, // Returns 1 by definition
+-128, // Returns subnormal value
+kValue.f32.negative.min, // Closest to returning 0 as possible
+...biasedRange(kValue.f32.negative.max, -127, 100),
+...biasedRange(kValue.f32.positive.min, 127, 100),
+...linearRange(128, 1023, 10) // Overflows f32, but not f64
+];
+
+// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not
+const f16_inputs = [
+0, // Returns 1 by definition
+-16, // Returns subnormal value
+kValue.f16.negative.min, // Closest to returning 0 as possible
+...biasedRange(kValue.f16.negative.max, -15, 100),
+...biasedRange(kValue.f16.positive.min, 15, 100),
+...linearRange(16, 1023, 10) // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp2', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('exp2'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('exp2'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/extractBits.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/extractBits.spec.js
new file mode 100644
index 0000000000..3faccc58a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/extractBits.spec.js
@@ -0,0 +1,337 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'extractBits' builtin function
+
+T is u32 or vecN<u32>
+@const fn extractBits(e: T, offset: u32, count: u32) -> T
+Reads bits from an integer, without sign extension.
+
+When T is a scalar type, then:
+ w is the bit width of T
+ o = min(offset,w)
+ c = min(count, w - o)
+
+The result is 0 if c is 0.
+Otherwise, bits 0..c-1 of the result are copied from bits o..o+c-1 of e.
+Other bits of the result are 0.
+Component-wise when T is a vector.
+
+
+T is i32 or vecN<i32>
+@const fn extractBits(e: T, offset: u32, count: u32) -> T
+Reads bits from an integer, with sign extension.
+
+When T is a scalar type, then:
+ w is the bit width of T
+ o = min(offset,w)
+ c = min(count, w - o)
+
+The result is 0 if c is 0.
+Otherwise, bits 0..c-1 of the result are copied from bits o..o+c-1 of e.
+Other bits of the result are the same as bit c-1 of the result.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32Bits,
+ TypeI32,
+ u32,
+ TypeU32,
+ u32Bits,
+ vec2,
+ vec3,
+ vec4,
+ TypeVec } from
+'../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('width', [1, 2, 3, 4])).
+fn(async (t) => {
+ const cfg = t.params;
+
+ const T = t.params.width === 1 ? TypeU32 : TypeVec(t.params.width, TypeU32);
+
+ const V = (x, y, z, w) => {
+ y = y === undefined ? x : y;
+ z = z === undefined ? x : z;
+ w = w === undefined ? x : w;
+
+ switch (t.params.width) {
+ case 1:
+ return u32Bits(x);
+ case 2:
+ return vec2(u32Bits(x), u32Bits(y));
+ case 3:
+ return vec3(u32Bits(x), u32Bits(y), u32Bits(z));
+ default:
+ return vec4(u32Bits(x), u32Bits(y), u32Bits(z), u32Bits(w));
+ }
+ };
+
+ const all_1 = V(0b11111111111111111111111111111111);
+ const all_0 = V(0b00000000000000000000000000000000);
+ const low_1 = V(0b00000000000000000000000000000001);
+ const high_1 = V(0b10000000000000000000000000000000);
+ const pattern = V(
+ 0b00000000000111011100000000000000,
+ 0b11111111111000000011111111111111,
+ 0b00000000010101010101000000000000,
+ 0b00000000001010101010100000000000
+ );
+
+ const cases = [
+ { input: [all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, u32(1), u32(10)], expected: all_0 },
+ { input: [all_0, u32(2), u32(5)], expected: all_0 },
+ { input: [all_0, u32(0), u32(1)], expected: all_0 },
+ { input: [all_0, u32(31), u32(1)], expected: all_0 },
+
+ { input: [all_1, u32(0), u32(32)], expected: all_1 },
+ {
+ input: [all_1, u32(1), u32(10)],
+ expected: V(0b00000000000000000000001111111111)
+ },
+ {
+ input: [all_1, u32(2), u32(5)],
+ expected: V(0b00000000000000000000000000011111)
+ },
+ { input: [all_1, u32(0), u32(1)], expected: low_1 },
+ { input: [all_1, u32(31), u32(1)], expected: low_1 },
+
+ // Patterns
+ { input: [pattern, u32(0), u32(32)], expected: pattern },
+ {
+ input: [pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00000000000011101110000000000000,
+ 0b01111111111100000001111111111111,
+ 0b00000000001010101010100000000000,
+ 0b00000000000101010101010000000000
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(18)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b00000000000000111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(7)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000001010101,
+ 0b00000000000000000000000000101010
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(4)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b00000000000000000000000000001010
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(3)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b00000000000000000000000000000010
+ )
+ },
+ {
+ input: [pattern, u32(18), u32(3)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b00000000000000000000000000000010
+ )
+ },
+ { input: [low_1, u32(0), u32(1)], expected: low_1 },
+ { input: [high_1, u32(31), u32(1)], expected: low_1 },
+
+ // Zero count
+ { input: [all_1, u32(0), u32(0)], expected: all_0 },
+ { input: [all_0, u32(0), u32(0)], expected: all_0 },
+ { input: [low_1, u32(0), u32(0)], expected: all_0 },
+ { input: [high_1, u32(31), u32(0)], expected: all_0 },
+ { input: [pattern, u32(0), u32(0)], expected: all_0 }];
+
+
+ if (t.params.inputSource !== 'const') {
+ cases.push(
+ ...[
+ // End overflow
+ { input: [low_1, u32(0), u32(99)], expected: low_1 },
+ { input: [high_1, u32(31), u32(99)], expected: low_1 },
+ { input: [pattern, u32(0), u32(99)], expected: pattern },
+ {
+ input: [pattern, u32(14), u32(99)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b00000000000000111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ )
+ }]
+
+ );
+ }
+
+ await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('width', [1, 2, 3, 4])).
+fn(async (t) => {
+ const cfg = t.params;
+
+ const T = t.params.width === 1 ? TypeI32 : TypeVec(t.params.width, TypeI32);
+
+ const V = (x, y, z, w) => {
+ y = y === undefined ? x : y;
+ z = z === undefined ? x : z;
+ w = w === undefined ? x : w;
+
+ switch (t.params.width) {
+ case 1:
+ return i32Bits(x);
+ case 2:
+ return vec2(i32Bits(x), i32Bits(y));
+ case 3:
+ return vec3(i32Bits(x), i32Bits(y), i32Bits(z));
+ default:
+ return vec4(i32Bits(x), i32Bits(y), i32Bits(z), i32Bits(w));
+ }
+ };
+
+ const all_1 = V(0b11111111111111111111111111111111);
+ const all_0 = V(0b00000000000000000000000000000000);
+ const low_1 = V(0b00000000000000000000000000000001);
+ const high_1 = V(0b10000000000000000000000000000000);
+ const pattern = V(
+ 0b00000000000111011100000000000000,
+ 0b11111111111000000011111111111111,
+ 0b00000000010101010101000000000000,
+ 0b00000000001010101010100000000000
+ );
+
+ const cases = [
+ { input: [all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, u32(1), u32(10)], expected: all_0 },
+ { input: [all_0, u32(2), u32(5)], expected: all_0 },
+ { input: [all_0, u32(0), u32(1)], expected: all_0 },
+ { input: [all_0, u32(31), u32(1)], expected: all_0 },
+
+ { input: [all_1, u32(0), u32(32)], expected: all_1 },
+ { input: [all_1, u32(1), u32(10)], expected: all_1 },
+ { input: [all_1, u32(2), u32(5)], expected: all_1 },
+ { input: [all_1, u32(0), u32(1)], expected: all_1 },
+ { input: [all_1, u32(31), u32(1)], expected: all_1 },
+
+ // Patterns
+ { input: [pattern, u32(0), u32(32)], expected: pattern },
+ {
+ input: [pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00000000000011101110000000000000,
+ 0b11111111111100000001111111111111,
+ 0b00000000001010101010100000000000,
+ 0b00000000000101010101010000000000
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(18)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b11111111111111111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(7)],
+ expected: V(
+ 0b11111111111111111111111111110111,
+ 0b00000000000000000000000000000000,
+ 0b11111111111111111111111111010101,
+ 0b00000000000000000000000000101010
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(4)],
+ expected: V(
+ 0b00000000000000000000000000000111,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000101,
+ 0b11111111111111111111111111111010
+ )
+ },
+ {
+ input: [pattern, u32(14), u32(3)],
+ expected: V(
+ 0b11111111111111111111111111111111,
+ 0b00000000000000000000000000000000,
+ 0b11111111111111111111111111111101,
+ 0b00000000000000000000000000000010
+ )
+ },
+ {
+ input: [pattern, u32(18), u32(3)],
+ expected: V(
+ 0b11111111111111111111111111111111,
+ 0b00000000000000000000000000000000,
+ 0b11111111111111111111111111111101,
+ 0b00000000000000000000000000000010
+ )
+ },
+ { input: [low_1, u32(0), u32(1)], expected: all_1 },
+ { input: [high_1, u32(31), u32(1)], expected: all_1 },
+
+ // Zero count
+ { input: [all_1, u32(0), u32(0)], expected: all_0 },
+ { input: [all_0, u32(0), u32(0)], expected: all_0 },
+ { input: [low_1, u32(0), u32(0)], expected: all_0 },
+ { input: [high_1, u32(31), u32(0)], expected: all_0 },
+ { input: [pattern, u32(0), u32(0)], expected: all_0 }];
+
+
+ if (t.params.inputSource !== 'const') {
+ cases.push(
+ ...[
+ // End overflow
+ { input: [low_1, u32(0), u32(99)], expected: low_1 },
+ { input: [high_1, u32(31), u32(99)], expected: all_1 },
+ { input: [pattern, u32(0), u32(99)], expected: pattern },
+ {
+ input: [pattern, u32(14), u32(99)],
+ expected: V(
+ 0b00000000000000000000000001110111,
+ 0b11111111111111111111111110000000,
+ 0b00000000000000000000000101010101,
+ 0b00000000000000000000000010101010
+ )
+ }]
+
+ );
+ }
+
+ await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/faceForward.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/faceForward.spec.js
new file mode 100644
index 0000000000..20ac9cbd2c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/faceForward.spec.js
@@ -0,0 +1,256 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'faceForward' builtin function
+
+T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+@const fn faceForward(e1: T ,e2: T ,e3: T ) -> T
+Returns e1 if dot(e2,e3) is negative, and -e1 otherwise.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ cartesianProduct,
+ sparseVectorF32Range,
+ sparseVectorF16Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// 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,
+x,
+y,
+z,
+check)
+{
+ 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 !== 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,
+xs,
+ys,
+zs,
+check)
+{
+ // 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 !== undefined);
+}
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].map((nonConst) => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return generateCases(
+ 'f16',
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('faceForward', {
+ ...f32_vec_cases,
+ ...f16_vec_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+unimplemented();
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('faceForward'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.js
new file mode 100644
index 0000000000..fd6b6359f1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.js
@@ -0,0 +1,350 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'firstLeadingBit' builtin function
+
+T is u32 or vecN<u32>
+@const fn firstLeadingBit(e: T ) -> T
+For scalar T, the result is: T(-1) if e is zero.
+Otherwise the position of the most significant 1 bit in e.
+Component-wise when T is a vector.
+
+T is i32 or vecN<i32>
+@const fn firstLeadingBit(e: T ) -> T
+For scalar T, the result is: -1 if e is 0 or -1.
+Otherwise the position of the most significant bit in e that is different from e’s sign bit.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('firstLeadingBit'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32(16) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32(20) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32(21) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32(22) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32(23) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32(24) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32(25) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32(27) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32(28) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32(29) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32(30) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(31) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32(15) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32(16) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32(17) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32(18) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32(19) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32(20) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32(21) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32(22) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32(23) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32(24) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32(25) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32(26) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32(27) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32(28) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32(29) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32(30) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32(31) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('firstLeadingBit'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
+
+ // Negative One
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(-1) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) },
+
+ // Positive: 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) },
+
+ // Negative: 0's after leading 0
+ { input: i32Bits(0b11111111111111111111111111111110), expected: i32(0) },
+ { input: i32Bits(0b11111111111111111111111111111100), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111000), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111110000), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111100000), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111000000), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111110000000), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111100000000), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111000000000), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111110000000000), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111100000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111000000000000), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111110000000000000), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111100000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111000000000000000), expected: i32(14) },
+ { input: i32Bits(0b11111111111111110000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11111111111111100000000000000000), expected: i32(16) },
+ { input: i32Bits(0b11111111111111000000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11111111111110000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111111111100000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b11111111111000000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111111110000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b11111111100000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11111111000000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b11111110000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11111100000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11111000000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b11110000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b11100000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b11000000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(30) },
+
+ // Positive: 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32(16) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32(21) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32(22) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32(23) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32(24) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32(25) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32(28) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32(29) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32(30) },
+
+ // Negative: 1's after leading 0
+ { input: i32Bits(0b11111111111111111111111111111101), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111011), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111110111), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111101111), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111011111), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111110111111), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111101111111), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111011111111), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111110111111111), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111101111111111), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111011111111111), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111110111111111111), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111101111111111111), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111011111111111111), expected: i32(14) },
+ { input: i32Bits(0b11111111111111110111111111111111), expected: i32(15) },
+ { input: i32Bits(0b11111111111111101111111111111111), expected: i32(16) },
+ { input: i32Bits(0b11111111111111011111111111111111), expected: i32(17) },
+ { input: i32Bits(0b11111111111110111111111111111111), expected: i32(18) },
+ { input: i32Bits(0b11111111111101111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b11111111111011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b11111111110111111111111111111111), expected: i32(21) },
+ { input: i32Bits(0b11111111101111111111111111111111), expected: i32(22) },
+ { input: i32Bits(0b11111111011111111111111111111111), expected: i32(23) },
+ { input: i32Bits(0b11111110111111111111111111111111), expected: i32(24) },
+ { input: i32Bits(0b11111101111111111111111111111111), expected: i32(25) },
+ { input: i32Bits(0b11111011111111111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b11110111111111111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b11101111111111111111111111111111), expected: i32(28) },
+ { input: i32Bits(0b11011111111111111111111111111111), expected: i32(29) },
+ { input: i32Bits(0b10111111111111111111111111111111), expected: i32(30) },
+
+ // Positive: random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32(15) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32(16) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32(17) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32(18) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32(19) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32(20) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32(21) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32(22) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32(23) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32(24) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32(25) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32(26) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32(27) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32(28) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32(29) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32(30) },
+
+ // Negative: random after leading 0
+ { input: i32Bits(0b11111111111111111111111111111010), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111110110), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111101101), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111011101), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111110111001), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111101101111), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111011111111), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111110111101111), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111101111111111), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111011111110001), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111110111011011101), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111101101101111111), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111011111111011111), expected: i32(14) },
+ { input: i32Bits(0b11111111111111110101111001110101), expected: i32(15) },
+ { input: i32Bits(0b11111111111111101101111011110111), expected: i32(16) },
+ { input: i32Bits(0b11111111111111011111111111110011), expected: i32(17) },
+ { input: i32Bits(0b11111111111110111111111110111111), expected: i32(18) },
+ { input: i32Bits(0b11111111111101111111011111111111), expected: i32(19) },
+ { input: i32Bits(0b11111111111011111111111111111111), expected: i32(20) },
+ { input: i32Bits(0b11111111110111110101011110111111), expected: i32(21) },
+ { input: i32Bits(0b11111111101111101111111111110111), expected: i32(22) },
+ { input: i32Bits(0b11111111011111111111010000101111), expected: i32(23) },
+ { input: i32Bits(0b11111110111111111111001111111011), expected: i32(24) },
+ { input: i32Bits(0b11111101111111011111101111111111), expected: i32(25) },
+ { input: i32Bits(0b11111011101011111011110111111011), expected: i32(26) },
+ { input: i32Bits(0b11110111111110111111111111111111), expected: i32(27) },
+ { input: i32Bits(0b11101111000000011011011110111111), expected: i32(28) },
+ { input: i32Bits(0b11011110101111011111111111111111), expected: i32(29) },
+ { input: i32Bits(0b10110110111111100111111110111101), expected: i32(30) }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.js
new file mode 100644
index 0000000000..73071c3808
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.js
@@ -0,0 +1,250 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'firstTrailingBit' builtin function
+
+S is i32, u32
+T is S or vecN<S>
+@const fn firstTrailingBit(e: T ) -> T
+For scalar T, the result is: T(-1) if e is zero.
+Otherwise the position of the least significant 1 bit in e.
+Component-wise when T is a vector.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('firstTrailingBit'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
+
+ // High bit
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32(31) },
+
+ // 0's before trailing 1
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32(0) },
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32(1) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32(2) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32(3) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32(4) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32(5) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32(6) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32(7) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32(8) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32(9) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32(10) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32(11) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32(12) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32(13) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32(14) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32(15) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32(16) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32(30) },
+
+ // 1's before trailing 1
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32(0) },
+ { input: u32Bits(0b11111111111111111111111111111110), expected: u32(1) },
+ { input: u32Bits(0b11111111111111111111111111111100), expected: u32(2) },
+ { input: u32Bits(0b11111111111111111111111111111000), expected: u32(3) },
+ { input: u32Bits(0b11111111111111111111111111110000), expected: u32(4) },
+ { input: u32Bits(0b11111111111111111111111111100000), expected: u32(5) },
+ { input: u32Bits(0b11111111111111111111111111000000), expected: u32(6) },
+ { input: u32Bits(0b11111111111111111111111110000000), expected: u32(7) },
+ { input: u32Bits(0b11111111111111111111111100000000), expected: u32(8) },
+ { input: u32Bits(0b11111111111111111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b11111111111111111111110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111111111111111100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111111111111111111000000000000), expected: u32(12) },
+ { input: u32Bits(0b11111111111111111110000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111111111111111100000000000000), expected: u32(14) },
+ { input: u32Bits(0b11111111111111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11111111111111110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b11111111111111100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11111111111111000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111111111110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b11111111111100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111111111000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b11111111110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11111111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b11111111000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11111110000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11111100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b11111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b11110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b11100000000000000000000000000000), expected: u32(29) },
+ { input: u32Bits(0b11000000000000000000000000000000), expected: u32(30) },
+
+ // random before trailing 1
+ { input: u32Bits(0b11110000001111111101111010001111), expected: u32(0) },
+ { input: u32Bits(0b11011110111111100101110011110010), expected: u32(1) },
+ { input: u32Bits(0b11110111011011111111010000111100), expected: u32(2) },
+ { input: u32Bits(0b11010011011101111111010011101000), expected: u32(3) },
+ { input: u32Bits(0b11010111110111110001111110110000), expected: u32(4) },
+ { input: u32Bits(0b11111101111101111110101111100000), expected: u32(5) },
+ { input: u32Bits(0b11111001111011111001111011000000), expected: u32(6) },
+ { input: u32Bits(0b11001110110111110111111010000000), expected: u32(7) },
+ { input: u32Bits(0b11101111011111101110101100000000), expected: u32(8) },
+ { input: u32Bits(0b11111101111011111111111000000000), expected: u32(9) },
+ { input: u32Bits(0b10011111011101110110110000000000), expected: u32(10) },
+ { input: u32Bits(0b11111111101101111011100000000000), expected: u32(11) },
+ { input: u32Bits(0b11111011010110111011000000000000), expected: u32(12) },
+ { input: u32Bits(0b00111101010000111010000000000000), expected: u32(13) },
+ { input: u32Bits(0b11111011110001101100000000000000), expected: u32(14) },
+ { input: u32Bits(0b10111111010111111000000000000000), expected: u32(15) },
+ { input: u32Bits(0b11011101111010110000000000000000), expected: u32(16) },
+ { input: u32Bits(0b01110100110110100000000000000000), expected: u32(17) },
+ { input: u32Bits(0b11100111001011000000000000000000), expected: u32(18) },
+ { input: u32Bits(0b11111001110110000000000000000000), expected: u32(19) },
+ { input: u32Bits(0b00110100100100000000000000000000), expected: u32(20) },
+ { input: u32Bits(0b11111010011000000000000000000000), expected: u32(21) },
+ { input: u32Bits(0b00000010110000000000000000000000), expected: u32(22) },
+ { input: u32Bits(0b11100111100000000000000000000000), expected: u32(23) },
+ { input: u32Bits(0b00101101000000000000000000000000), expected: u32(24) },
+ { input: u32Bits(0b11011010000000000000000000000000), expected: u32(25) },
+ { input: u32Bits(0b11010100000000000000000000000000), expected: u32(26) },
+ { input: u32Bits(0b10111000000000000000000000000000), expected: u32(27) },
+ { input: u32Bits(0b01110000000000000000000000000000), expected: u32(28) },
+ { input: u32Bits(0b10100000000000000000000000000000), expected: u32(29) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ await run(t, builtin('firstTrailingBit'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
+
+ // High bit
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32(31) },
+
+ // 0's before trailing 1
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32(0) },
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32(1) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32(2) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32(3) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32(4) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32(5) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32(6) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32(7) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32(8) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32(9) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32(10) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32(11) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32(12) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32(13) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32(14) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32(15) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32(16) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32(30) },
+
+ // 1's before trailing 1
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32(0) },
+ { input: i32Bits(0b11111111111111111111111111111110), expected: i32(1) },
+ { input: i32Bits(0b11111111111111111111111111111100), expected: i32(2) },
+ { input: i32Bits(0b11111111111111111111111111111000), expected: i32(3) },
+ { input: i32Bits(0b11111111111111111111111111110000), expected: i32(4) },
+ { input: i32Bits(0b11111111111111111111111111100000), expected: i32(5) },
+ { input: i32Bits(0b11111111111111111111111111000000), expected: i32(6) },
+ { input: i32Bits(0b11111111111111111111111110000000), expected: i32(7) },
+ { input: i32Bits(0b11111111111111111111111100000000), expected: i32(8) },
+ { input: i32Bits(0b11111111111111111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b11111111111111111111110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111111111111111100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111111111111111111000000000000), expected: i32(12) },
+ { input: i32Bits(0b11111111111111111110000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111111111111111100000000000000), expected: i32(14) },
+ { input: i32Bits(0b11111111111111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11111111111111110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b11111111111111100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11111111111111000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111111111110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b11111111111100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111111111000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b11111111110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11111111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b11111111000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11111110000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11111100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b11111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b11110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b11100000000000000000000000000000), expected: i32(29) },
+ { input: i32Bits(0b11000000000000000000000000000000), expected: i32(30) },
+
+ // random before trailing 1
+ { input: i32Bits(0b11110000001111111101111010001111), expected: i32(0) },
+ { input: i32Bits(0b11011110111111100101110011110010), expected: i32(1) },
+ { input: i32Bits(0b11110111011011111111010000111100), expected: i32(2) },
+ { input: i32Bits(0b11010011011101111111010011101000), expected: i32(3) },
+ { input: i32Bits(0b11010111110111110001111110110000), expected: i32(4) },
+ { input: i32Bits(0b11111101111101111110101111100000), expected: i32(5) },
+ { input: i32Bits(0b11111001111011111001111011000000), expected: i32(6) },
+ { input: i32Bits(0b11001110110111110111111010000000), expected: i32(7) },
+ { input: i32Bits(0b11101111011111101110101100000000), expected: i32(8) },
+ { input: i32Bits(0b11111101111011111111111000000000), expected: i32(9) },
+ { input: i32Bits(0b10011111011101110110110000000000), expected: i32(10) },
+ { input: i32Bits(0b11111111101101111011100000000000), expected: i32(11) },
+ { input: i32Bits(0b11111011010110111011000000000000), expected: i32(12) },
+ { input: i32Bits(0b00111101010000111010000000000000), expected: i32(13) },
+ { input: i32Bits(0b11111011110001101100000000000000), expected: i32(14) },
+ { input: i32Bits(0b10111111010111111000000000000000), expected: i32(15) },
+ { input: i32Bits(0b11011101111010110000000000000000), expected: i32(16) },
+ { input: i32Bits(0b01110100110110100000000000000000), expected: i32(17) },
+ { input: i32Bits(0b11100111001011000000000000000000), expected: i32(18) },
+ { input: i32Bits(0b11111001110110000000000000000000), expected: i32(19) },
+ { input: i32Bits(0b00110100100100000000000000000000), expected: i32(20) },
+ { input: i32Bits(0b11111010011000000000000000000000), expected: i32(21) },
+ { input: i32Bits(0b00000010110000000000000000000000), expected: i32(22) },
+ { input: i32Bits(0b11100111100000000000000000000000), expected: i32(23) },
+ { input: i32Bits(0b00101101000000000000000000000000), expected: i32(24) },
+ { input: i32Bits(0b11011010000000000000000000000000), expected: i32(25) },
+ { input: i32Bits(0b11010100000000000000000000000000), expected: i32(26) },
+ { input: i32Bits(0b10111000000000000000000000000000), expected: i32(27) },
+ { input: i32Bits(0b01110000000000000000000000000000), expected: i32(28) },
+ { input: i32Bits(0b10100000000000000000000000000000), expected: i32(29) }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/floor.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/floor.spec.js
new file mode 100644
index 0000000000..322fffe0eb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/floor.spec.js
@@ -0,0 +1,96 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'floor' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn floor(e: T ) -> T
+Returns the floor of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
+
+export const d = makeCaseCache('floor', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ ...kSmallMagnitudeTestValues,
+ ...fullF32Range(),
+ 0x8000_0000 // https://github.com/gpuweb/cts/issues/2766
+ ],
+ 'unfiltered',
+ FP.f32.floorInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ ...kSmallMagnitudeTestValues,
+ ...fullF16Range(),
+ 0x8000 // https://github.com/gpuweb/cts/issues/2766
+ ],
+ 'unfiltered',
+ FP.f16.floorInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [
+ ...kSmallMagnitudeTestValues,
+ ...fullF64Range(),
+ 0x8000_0000_0000_0000 // https://github.com/gpuweb/cts/issues/2766
+ ],
+ 'unfiltered',
+ FP.abstract.floorInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(t, abstractBuiltin('floor'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('floor'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('floor'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fma.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fma.spec.js
new file mode 100644
index 0000000000..f5bac2d922
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fma.spec.js
@@ -0,0 +1,113 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'fma' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn fma(e1: T ,e2: T ,e3: T ) -> T
+Returns e1 * e2 + e3. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('fma', {
+ f32_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'finite',
+ FP.f32.fmaInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'unfiltered',
+ FP.f32.fmaInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'finite',
+ FP.f16.fmaInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'unfiltered',
+ FP.f16.fmaInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarTripleToIntervalCases(
+ sparseF64Range(),
+ sparseF64Range(),
+ sparseF64Range(),
+ 'finite',
+ FP.abstract.fmaInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('fma'),
+ [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('fma'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('fma'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fract.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fract.spec.js
new file mode 100644
index 0000000000..8f85d604bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fract.spec.js
@@ -0,0 +1,103 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'fract' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn fract(e: T ) -> T
+Returns the fractional part of e, computed as e - floor(e).
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('fract', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ 0.5, // 0.5 -> 0.5
+ 0.9, // ~0.9 -> ~0.9
+ 1, // 1 -> 0
+ 2, // 2 -> 0
+ 1.11, // ~1.11 -> ~0.11
+ 10.0001, // ~10.0001 -> ~0.0001
+ -0.1, // ~-0.1 -> ~0.9
+ -0.5, // -0.5 -> 0.5
+ -0.9, // ~-0.9 -> ~0.1
+ -1, // -1 -> 0
+ -2, // -2 -> 0
+ -1.11, // ~-1.11 -> ~0.89
+ -10.0001, // -10.0001 -> ~0.9999
+ 0x80000000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.fractInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ 0.5, // 0.5 -> 0.5
+ 0.9, // ~0.9 -> ~0.9
+ 1, // 1 -> 0
+ 2, // 2 -> 0
+ 1.11, // ~1.11 -> ~0.11
+ 10.0078125, // 10.0078125 -> 0.0078125
+ -0.1, // ~-0.1 -> ~0.9
+ -0.5, // -0.5 -> 0.5
+ -0.9, // ~-0.9 -> ~0.1
+ -1, // -1 -> 0
+ -2, // -2 -> 0
+ -1.11, // ~-1.11 -> ~0.89
+ -10.0078125, // -10.0078125 -> 0.9921875
+ 658.5, // 658.5 -> 0.5
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.fractInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('fract'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('fract'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/frexp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/frexp.spec.js
new file mode 100644
index 0000000000..962de5456e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/frexp.spec.js
@@ -0,0 +1,475 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'frexp' builtin function
+
+S is f32 or f16
+T is S or vecN<S>
+
+@const fn frexp(e: T) -> result_struct
+
+Splits e into a significand and exponent of the form significand * 2^exponent.
+Returns the result_struct for the appropriate overload.
+
+
+The magnitude of the significand is in the range of [0.5, 1.0) or 0.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { skipUndefined } from '../../../../../util/compare.js';
+import {
+ i32,
+
+ toVector,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeVec } from
+
+'../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ frexp,
+ fullF16Range,
+ fullF32Range,
+ vectorF16Range,
+ vectorF32Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import {
+ allInputSources,
+ basicExpressionBuilder,
+
+ run } from
+
+'../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure */
+function fractBuilder() {
+ return basicExpressionBuilder((value) => `frexp(${value}).fract`);
+}
+
+/* @returns an ShaderBuilder that evaluates frexp and returns .exp from the result structure */
+function expBuilder() {
+ return basicExpressionBuilder((value) => `frexp(${value}).exp`);
+}
+
+/* @returns a fract Case for a given scalar or vector input */
+function makeVectorCaseFract(v, trait) {
+ const fp = FP[trait];
+ let toInput;
+ let toOutput;
+ if (v instanceof Array) {
+ // Input is vector
+ toInput = (n) => toVector(n, fp.scalarBuilder);
+ toOutput = (n) => toVector(n, fp.scalarBuilder);
+ } else {
+ // Input is scalar, also wrap it in an array.
+ v = [v];
+ toInput = (n) => fp.scalarBuilder(n[0]);
+ toOutput = (n) => fp.scalarBuilder(n[0]);
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some((e) => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map((e) => {
+ return frexp(e, trait).fract;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+/* @returns an exp Case for a given scalar or vector input */
+function makeVectorCaseExp(v, trait) {
+ const fp = FP[trait];
+ let toInput;
+ let toOutput;
+ if (v instanceof Array) {
+ // Input is vector
+ toInput = (n) => toVector(n, fp.scalarBuilder);
+ toOutput = (n) => toVector(n, i32);
+ } else {
+ // Input is scalar, also wrap it in an array.
+ v = [v];
+ toInput = (n) => fp.scalarBuilder(n[0]);
+ toOutput = (n) => i32(n[0]);
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some((e) => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map((e) => {
+ return frexp(e, trait).exp;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+export const d = makeCaseCache('frexp', {
+ f32_fract: () => {
+ return fullF32Range().map((v) => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_exp: () => {
+ return fullF32Range().map((v) => makeVectorCaseExp(v, 'f32'));
+ },
+ f32_vec2_fract: () => {
+ return vectorF32Range(2).map((v) => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_vec2_exp: () => {
+ return vectorF32Range(2).map((v) => makeVectorCaseExp(v, 'f32'));
+ },
+ f32_vec3_fract: () => {
+ return vectorF32Range(3).map((v) => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_vec3_exp: () => {
+ return vectorF32Range(3).map((v) => makeVectorCaseExp(v, 'f32'));
+ },
+ f32_vec4_fract: () => {
+ return vectorF32Range(4).map((v) => makeVectorCaseFract(v, 'f32'));
+ },
+ f32_vec4_exp: () => {
+ return vectorF32Range(4).map((v) => makeVectorCaseExp(v, 'f32'));
+ },
+ f16_fract: () => {
+ return fullF16Range().map((v) => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_exp: () => {
+ return fullF16Range().map((v) => makeVectorCaseExp(v, 'f16'));
+ },
+ f16_vec2_fract: () => {
+ return vectorF16Range(2).map((v) => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_vec2_exp: () => {
+ return vectorF16Range(2).map((v) => makeVectorCaseExp(v, 'f16'));
+ },
+ f16_vec3_fract: () => {
+ return vectorF16Range(3).map((v) => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_vec3_exp: () => {
+ return vectorF16Range(3).map((v) => makeVectorCaseExp(v, 'f16'));
+ },
+ f16_vec4_fract: () => {
+ return vectorF16Range(4).map((v) => makeVectorCaseFract(v, 'f16'));
+ },
+ f16_vec4_exp: () => {
+ return vectorF16Range(4).map((v) => makeVectorCaseExp(v, 'f16'));
+ }
+});
+
+g.test('f32_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f32
+
+struct __frexp_result_f32 {
+ fract : f32, // fract part
+ exp : i32 // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_fract');
+ await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f32
+
+struct __frexp_result_f32 {
+ fract : f32, // fract part
+ exp : i32 // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_exp');
+ await run(t, expBuilder(), [TypeF32], TypeI32, t.params, cases);
+});
+
+g.test('f32_vec2_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f32>
+
+struct __frexp_result_vec2_f32 {
+ fract : vec2<f32>, // fract part
+ exp : vec2<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec2_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f32>
+
+struct __frexp_result_vec2_f32 {
+ fract : vec2<f32>, // fractional part
+ exp : vec2<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec2_exp');
+ await run(t, expBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeI32), t.params, cases);
+});
+
+g.test('f32_vec3_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f32>
+
+struct __frexp_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec3_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f32>
+
+struct __frexp_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec3_exp');
+ await run(t, expBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeI32), t.params, cases);
+});
+
+g.test('f32_vec4_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f32>
+
+struct __frexp_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec4_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f32>
+
+struct __frexp_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec4_exp');
+ await run(t, expBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeI32), t.params, cases);
+});
+
+g.test('f16_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f16
+
+struct __frexp_result_f16 {
+ fract : f16, // fract part
+ exp : i32 // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_fract');
+ await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f16
+
+struct __frexp_result_f16 {
+ fract : f16, // fract part
+ exp : i32 // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_exp');
+ await run(t, expBuilder(), [TypeF16], TypeI32, t.params, cases);
+});
+
+g.test('f16_vec2_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f16>
+
+struct __frexp_result_vec2_f16 {
+ fract : vec2<f16>, // fract part
+ exp : vec2<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec2_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f16>
+
+struct __frexp_result_vec2_f16 {
+ fract : vec2<f16>, // fractional part
+ exp : vec2<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec2_exp');
+ await run(t, expBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeI32), t.params, cases);
+});
+
+g.test('f16_vec3_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f16>
+
+struct __frexp_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec3_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f16>
+
+struct __frexp_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ exp : vec3<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec3_exp');
+ await run(t, expBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeI32), t.params, cases);
+});
+
+g.test('f16_vec4_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f16>
+
+struct __frexp_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec4_exp').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f16>
+
+struct __frexp_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ exp : vec4<i32> // exponent part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec4_exp');
+ await run(t, expBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeI32), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidth.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidth.spec.js
new file mode 100644
index 0000000000..2ec40557bb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidth.spec.js
@@ -0,0 +1,21 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'fwidth' builtin function
+
+T is f32 or vecN<f32>
+fn fwidth(e:T) ->T
+Returns abs(dpdx(e)) + abs(dpdy(e)).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.js
new file mode 100644
index 0000000000..21c4cce923
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthCoarse.spec.js
@@ -0,0 +1,21 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'fwidthCoarse' builtin function
+
+T is f32 or vecN<f32>
+fn fwidthCoarse(e:T) ->T
+Returns abs(dpdxCoarse(e)) + abs(dpdyCoarse(e)).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.js
new file mode 100644
index 0000000000..637967371e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/fwidthFine.spec.js
@@ -0,0 +1,21 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'fwidthFine' builtin function
+
+T is f32 or vecN<f32>
+fn fwidthFine(e:T) ->T
+Returns abs(dpdxFine(e)) + abs(dpdyFine(e)).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { allInputSources } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/insertBits.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/insertBits.spec.js
new file mode 100644
index 0000000000..1af24a5189
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/insertBits.spec.js
@@ -0,0 +1,386 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'insertBits' builtin function
+
+S is i32 or u32
+T is S or vecN<S>
+@const fn insertBits(e: T, newbits:T, offset: u32, count: u32) -> T Sets bits in an integer.
+
+When T is a scalar type, then:
+ w is the bit width of T
+ o = min(offset,w)
+ c = min(count, w - o)
+
+The result is e if c is 0.
+Otherwise, bits o..o+c-1 of the result are copied from bits 0..c-1 of newbits.
+Other bits of the result are copied from e.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32Bits,
+ TypeI32,
+ u32,
+ TypeU32,
+ u32Bits,
+ vec2,
+ vec3,
+ vec4,
+ TypeVec } from
+'../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('integer').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`integer tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('signed', [false, true]).
+combine('width', [1, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+ const scalarType = t.params.signed ? TypeI32 : TypeU32;
+ const T = t.params.width === 1 ? scalarType : TypeVec(t.params.width, scalarType);
+
+ const V = (x, y, z, w) => {
+ y = y === undefined ? x : y;
+ z = z === undefined ? x : z;
+ w = w === undefined ? x : w;
+
+ if (t.params.signed) {
+ switch (t.params.width) {
+ case 1:
+ return i32Bits(x);
+ case 2:
+ return vec2(i32Bits(x), i32Bits(y));
+ case 3:
+ return vec3(i32Bits(x), i32Bits(y), i32Bits(z));
+ default:
+ return vec4(i32Bits(x), i32Bits(y), i32Bits(z), i32Bits(w));
+ }
+ } else {
+ switch (t.params.width) {
+ case 1:
+ return u32Bits(x);
+ case 2:
+ return vec2(u32Bits(x), u32Bits(y));
+ case 3:
+ return vec3(u32Bits(x), u32Bits(y), u32Bits(z));
+ default:
+ return vec4(u32Bits(x), u32Bits(y), u32Bits(z), u32Bits(w));
+ }
+ }
+ };
+
+ const all_1 = V(0b11111111111111111111111111111111);
+ const all_0 = V(0b00000000000000000000000000000000);
+ const low_1 = V(0b00000000000000000000000000000001);
+ const low_0 = V(0b11111111111111111111111111111110);
+ const high_1 = V(0b10000000000000000000000000000000);
+ const high_0 = V(0b01111111111111111111111111111111);
+ const pattern = V(
+ 0b10001001010100100010010100100010,
+ 0b11001110001100111000110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ );
+
+ const cases = [
+ { input: [all_0, all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, all_0, u32(1), u32(10)], expected: all_0 },
+ { input: [all_0, all_0, u32(2), u32(5)], expected: all_0 },
+ { input: [all_0, all_0, u32(0), u32(1)], expected: all_0 },
+ { input: [all_0, all_0, u32(31), u32(1)], expected: all_0 },
+
+ { input: [all_0, all_1, u32(0), u32(32)], expected: all_1 },
+ { input: [all_1, all_0, u32(0), u32(32)], expected: all_0 },
+ { input: [all_0, all_1, u32(0), u32(1)], expected: low_1 },
+ { input: [all_1, all_0, u32(0), u32(1)], expected: low_0 },
+ { input: [all_0, all_1, u32(31), u32(1)], expected: high_1 },
+ { input: [all_1, all_0, u32(31), u32(1)], expected: high_0 },
+ { input: [all_0, all_1, u32(1), u32(10)], expected: V(0b00000000000000000000011111111110) },
+ { input: [all_1, all_0, u32(1), u32(10)], expected: V(0b11111111111111111111100000000001) },
+ { input: [all_0, all_1, u32(2), u32(5)], expected: V(0b00000000000000000000000001111100) },
+ { input: [all_1, all_0, u32(2), u32(5)], expected: V(0b11111111111111111111111110000011) },
+
+ // Patterns
+ { input: [all_0, pattern, u32(0), u32(32)], expected: pattern },
+ { input: [all_1, pattern, u32(0), u32(32)], expected: pattern },
+ {
+ input: [all_0, pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00010010101001000100101001000100,
+ 0b10011100011001110001100111000110,
+ 0b01010101010101010101010101010100,
+ 0b10101010101010101010101010101010
+ )
+ },
+ {
+ input: [all_1, pattern, u32(1), u32(31)],
+ expected: V(
+ 0b00010010101001000100101001000101,
+ 0b10011100011001110001100111000111,
+ 0b01010101010101010101010101010101,
+ 0b10101010101010101010101010101011
+ )
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(18)],
+ expected: V(
+ 0b10001001010010001000000000000000,
+ 0b11100011001110001100000000000000,
+ 0b10101010101010101000000000000000,
+ 0b01010101010101010100000000000000
+ )
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(18)],
+ expected: V(
+ 0b10001001010010001011111111111111,
+ 0b11100011001110001111111111111111,
+ 0b10101010101010101011111111111111,
+ 0b01010101010101010111111111111111
+ )
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(7)],
+ expected: V(
+ 0b00000000000010001000000000000000,
+ 0b00000000000110001100000000000000,
+ 0b00000000000010101000000000000000,
+ 0b00000000000101010100000000000000
+ )
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(7)],
+ expected: V(
+ 0b11111111111010001011111111111111,
+ 0b11111111111110001111111111111111,
+ 0b11111111111010101011111111111111,
+ 0b11111111111101010111111111111111
+ )
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(4)],
+ expected: V(
+ 0b00000000000000001000000000000000,
+ 0b00000000000000001100000000000000,
+ 0b00000000000000101000000000000000,
+ 0b00000000000000010100000000000000
+ )
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(4)],
+ expected: V(
+ 0b11111111111111001011111111111111,
+ 0b11111111111111001111111111111111,
+ 0b11111111111111101011111111111111,
+ 0b11111111111111010111111111111111
+ )
+ },
+ {
+ input: [all_0, pattern, u32(14), u32(3)],
+ expected: V(
+ 0b00000000000000001000000000000000,
+ 0b00000000000000001100000000000000,
+ 0b00000000000000001000000000000000,
+ 0b00000000000000010100000000000000
+ )
+ },
+ {
+ input: [all_1, pattern, u32(14), u32(3)],
+ expected: V(
+ 0b11111111111111101011111111111111,
+ 0b11111111111111101111111111111111,
+ 0b11111111111111101011111111111111,
+ 0b11111111111111110111111111111111
+ )
+ },
+ {
+ input: [all_0, pattern, u32(18), u32(3)],
+ expected: V(
+ 0b00000000000010000000000000000000,
+ 0b00000000000011000000000000000000,
+ 0b00000000000010000000000000000000,
+ 0b00000000000101000000000000000000
+ )
+ },
+ {
+ input: [all_1, pattern, u32(18), u32(3)],
+ expected: V(
+ 0b11111111111010111111111111111111,
+ 0b11111111111011111111111111111111,
+ 0b11111111111010111111111111111111,
+ 0b11111111111101111111111111111111
+ )
+ },
+ {
+ input: [pattern, all_0, u32(1), u32(31)],
+ expected: V(
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000001,
+ 0b00000000000000000000000000000000,
+ 0b00000000000000000000000000000001
+ )
+ },
+ {
+ input: [pattern, all_1, u32(1), u32(31)],
+ expected: V(
+ 0b11111111111111111111111111111110,
+ 0b11111111111111111111111111111111,
+ 0b11111111111111111111111111111110,
+ 0b11111111111111111111111111111111
+ )
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(18)],
+ expected: V(
+ 0b00000000000000000010010100100010,
+ 0b00000000000000000000110011100011,
+ 0b00000000000000000010101010101010,
+ 0b00000000000000000001010101010101
+ )
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(18)],
+ expected: V(
+ 0b11111111111111111110010100100010,
+ 0b11111111111111111100110011100011,
+ 0b11111111111111111110101010101010,
+ 0b11111111111111111101010101010101
+ )
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(7)],
+ expected: V(
+ 0b10001001010000000010010100100010,
+ 0b11001110001000000000110011100011,
+ 0b10101010101000000010101010101010,
+ 0b01010101010000000001010101010101
+ )
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(7)],
+ expected: V(
+ 0b10001001010111111110010100100010,
+ 0b11001110001111111100110011100011,
+ 0b10101010101111111110101010101010,
+ 0b01010101010111111101010101010101
+ )
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(4)],
+ expected: V(
+ 0b10001001010100000010010100100010,
+ 0b11001110001100000000110011100011,
+ 0b10101010101010000010101010101010,
+ 0b01010101010101000001010101010101
+ )
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(4)],
+ expected: V(
+ 0b10001001010100111110010100100010,
+ 0b11001110001100111100110011100011,
+ 0b10101010101010111110101010101010,
+ 0b01010101010101111101010101010101
+ )
+ },
+ {
+ input: [pattern, all_0, u32(14), u32(3)],
+ expected: V(
+ 0b10001001010100100010010100100010,
+ 0b11001110001100100000110011100011,
+ 0b10101010101010100010101010101010,
+ 0b01010101010101000001010101010101
+ )
+ },
+ {
+ input: [pattern, all_1, u32(14), u32(3)],
+ expected: V(
+ 0b10001001010100111110010100100010,
+ 0b11001110001100111100110011100011,
+ 0b10101010101010111110101010101010,
+ 0b01010101010101011101010101010101
+ )
+ },
+ {
+ input: [pattern, all_0, u32(18), u32(3)],
+ expected: V(
+ 0b10001001010000100010010100100010,
+ 0b11001110001000111000110011100011,
+ 0b10101010101000101010101010101010,
+ 0b01010101010000010101010101010101
+ )
+ },
+ {
+ input: [pattern, all_1, u32(18), u32(3)],
+ expected: V(
+ 0b10001001010111100010010100100010,
+ 0b11001110001111111000110011100011,
+ 0b10101010101111101010101010101010,
+ 0b01010101010111010101010101010101
+ )
+ },
+ {
+ input: [pattern, pattern, u32(18), u32(3)],
+ expected: V(
+ 0b10001001010010100010010100100010,
+ 0b11001110001011111000110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ )
+ },
+ {
+ input: [pattern, pattern, u32(14), u32(7)],
+ expected: V(
+ 0b10001001010010001010010100100010,
+ 0b11001110001110001100110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ )
+ },
+
+ // Zero count
+ { input: [pattern, all_1, u32(0), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(1), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(2), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(31), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(32), u32(0)], expected: pattern },
+ { input: [pattern, all_1, u32(0), u32(0)], expected: pattern }];
+
+
+ if (t.params.inputSource !== 'const') {
+ cases.push(
+ ...[
+ // Start overflow
+ { input: [all_0, pattern, u32(50), u32(3)], expected: all_0 },
+ { input: [all_1, pattern, u32(50), u32(3)], expected: all_1 },
+ { input: [pattern, pattern, u32(50), u32(3)], expected: pattern },
+
+ // End overflow
+ { input: [all_0, pattern, u32(0), u32(99)], expected: pattern },
+ { input: [all_1, pattern, u32(0), u32(99)], expected: pattern },
+ { input: [all_0, low_1, u32(31), u32(99)], expected: high_1 },
+ {
+ input: [pattern, pattern, u32(20), u32(99)],
+ expected: V(
+ 0b01010010001000100010010100100010,
+ 0b11001110001100111000110011100011,
+ 0b10101010101010101010101010101010,
+ 0b01010101010101010101010101010101
+ )
+ }]
+
+ );
+ }
+
+ await run(t, builtin('insertBits'), [T, T, TypeU32, TypeU32], T, cfg, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.js
new file mode 100644
index 0000000000..c24121c716
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.js
@@ -0,0 +1,81 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'inverseSqrt' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn inverseSqrt(e: T ) -> T
+Returns the reciprocal of sqrt(e). Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('inverseSqrt', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f32.positive.min, 1, 100),
+ // 1 <= x < 2^32, biased towards 1
+ ...biasedRange(1, 2 ** 32, 1000)],
+
+ 'unfiltered',
+ FP.f32.inverseSqrtInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f16.positive.min, 1, 100),
+ // 1 <= x < 2^15, biased towards 1
+ ...biasedRange(1, 2 ** 15, 1000)],
+
+ 'unfiltered',
+ FP.f16.inverseSqrtInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('inverseSqrt'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('inverseSqrt'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ldexp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ldexp.spec.js
new file mode 100644
index 0000000000..a91e9d73ff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/ldexp.spec.js
@@ -0,0 +1,121 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'ldexp' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+
+K is AbstractInt, i32
+I is K or vecN<K>, where
+ I is a scalar if T is a scalar, or a vector when T is a vector
+
+@const fn ldexp(e1: T ,e2: I ) -> T
+Returns e1 * 2^e2. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { i32, TypeF32, TypeF16, TypeI32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ biasedRange,
+ quantizeToI32,
+ sparseF32Range,
+ sparseI32Range,
+ sparseF16Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const bias = {
+ f32: 127,
+ f16: 15
+};
+
+// 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, e1, e2) => {
+ const FPTrait = FP[trait];
+ e1 = FPTrait.quantize(e1);
+ // e2 should be in i32 range for the convinience.
+ assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range');
+ e2 = quantizeToI32(e2);
+
+ const expected = FPTrait.ldexpInterval(e1, e2);
+
+ // Result may be zero if e2 + bias <= 0
+ if (e2 + bias[trait] <= 0) {
+ return {
+ input: [FPTrait.scalarBuilder(e1), i32(e2)],
+ expected: anyOf(expected, FPTrait.constants().zeroInterval)
+ };
+ }
+
+ return { input: [FPTrait.scalarBuilder(e1), i32(e2)], expected };
+};
+
+export const d = makeCaseCache('ldexp', {
+ f32_non_const: () => {
+ return sparseF32Range().flatMap((e1) => sparseI32Range().map((e2) => makeCase('f32', e1, e2)));
+ },
+ f32_const: () => {
+ return sparseF32Range().flatMap((e1) =>
+ biasedRange(-bias.f32 - 10, bias.f32 + 1, 10).flatMap((e2) =>
+ FP.f32.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f32', e1, e2) : []
+ )
+ );
+ },
+ f16_non_const: () => {
+ return sparseF16Range().flatMap((e1) => sparseI32Range().map((e2) => makeCase('f16', e1, e2)));
+ },
+ f16_const: () => {
+ return sparseF16Range().flatMap((e1) =>
+ biasedRange(-bias.f16 - 10, bias.f16 + 1, 10).flatMap((e2) =>
+ FP.f16.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f16', e1, e2) : []
+ )
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('ldexp'), [TypeF32, TypeI32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('ldexp'), [TypeF16, TypeI32], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/length.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/length.spec.js
new file mode 100644
index 0000000000..fa42f7e504
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/length.spec.js
@@ -0,0 +1,178 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'length' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn length(e: T ) -> f32
+Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 + ...) if T is a vector).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullF16Range,
+ vectorF32Range,
+ vectorF16Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].map((nonConst) => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorToIntervalCases(
+ vectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.lengthInterval
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('length', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.lengthInterval
+ );
+ },
+ ...f32_vec_cases,
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.lengthInterval
+ );
+ },
+ ...f16_vec_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('length'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(2, TypeF32)], TypeF32, t.params, cases);
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(3, TypeF32)], TypeF32, t.params, cases);
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(4, TypeF32)], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('length'), [TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(2, TypeF16)], TypeF16, t.params, cases);
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(3, TypeF16)], TypeF16, t.params, cases);
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(t, builtin('length'), [TypeVec(4, TypeF16)], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log.spec.js
new file mode 100644
index 0000000000..5135d0ec02
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log.spec.js
@@ -0,0 +1,89 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'log' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn log(e: T ) -> T
+Returns the natural logarithm of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+const f32_inputs = [
+...linearRange(kValue.f32.positive.min, 0.5, 20),
+...linearRange(0.5, 2.0, 20),
+...biasedRange(2.0, 2 ** 32, 1000),
+...fullF32Range()];
+
+const f16_inputs = [
+...linearRange(kValue.f16.positive.min, 0.5, 20),
+...linearRange(0.5, 2.0, 20),
+...biasedRange(2.0, 2 ** 32, 1000),
+...fullF16Range()];
+
+
+export const d = makeCaseCache('log', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.logInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.logInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.logInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.logInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('log'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('log'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log2.spec.js
new file mode 100644
index 0000000000..c63b2c4224
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/log2.spec.js
@@ -0,0 +1,89 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'log2' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn log2(e: T ) -> T
+Returns the base-2 logarithm of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+const f32_inputs = [
+...linearRange(kValue.f32.positive.min, 0.5, 20),
+...linearRange(0.5, 2.0, 20),
+...biasedRange(2.0, 2 ** 32, 1000),
+...fullF32Range()];
+
+const f16_inputs = [
+...linearRange(kValue.f16.positive.min, 0.5, 20),
+...linearRange(0.5, 2.0, 20),
+...biasedRange(2.0, 2 ** 32, 1000),
+...fullF16Range()];
+
+
+export const d = makeCaseCache('log2', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.log2Interval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.log2Interval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.log2Interval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.log2Interval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('log2'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('log2'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/max.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/max.spec.js
new file mode 100644
index 0000000000..53abc03c33
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/max.spec.js
@@ -0,0 +1,165 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'max' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn max(e1: T ,e2: T) -> T
+Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector.
+
+S is AbstractFloat, f32, f16
+T is vecN<S>
+@const fn max(e1: T ,e2: T) -> T
+Returns e2 if e1 is less than e2, and e1 otherwise.
+If one operand is a NaN, the other is returned.
+If both operands are NaNs, a NaN is returned.
+Component-wise when T is a vector.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ u32,
+ TypeAbstractFloat } from
+'../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+/** Generate set of max test cases from list of interesting values */
+function generateTestCases(
+values,
+makeCase)
+{
+ const cases = new Array();
+ values.forEach((e) => {
+ values.forEach((f) => {
+ cases.push(makeCase(e, f));
+ });
+ });
+ return cases;
+}
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('max', {
+ f32: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.maxInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.maxInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseF64Range(),
+ sparseF64Range(),
+ 'unfiltered',
+ FP.abstract.maxInterval
+ );
+ }
+});
+
+g.test('abstract_int').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`abstract int tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const makeCase = (x, y) => {
+ return { input: [u32(x), u32(y)], expected: u32(Math.max(x, y)) };
+ };
+
+ const test_values = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('max'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const makeCase = (x, y) => {
+ return { input: [i32(x), i32(y)], expected: i32(Math.max(x, y)) };
+ };
+
+ const test_values = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('max'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('max'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('max'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('max'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/min.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/min.spec.js
new file mode 100644
index 0000000000..0b1bc58c3e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/min.spec.js
@@ -0,0 +1,164 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'min' builtin function
+
+S is AbstractInt, i32, or u32
+T is S or vecN<S>
+@const fn min(e1: T ,e2: T) -> T
+Returns e1 if e1 is less than e2, and e2 otherwise. Component-wise when T is a vector.
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn min(e1: T ,e2: T) -> T
+Returns e2 if e2 is less than e1, and e1 otherwise.
+If one operand is a NaN, the other is returned.
+If both operands are NaNs, a NaN is returned.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ u32,
+ TypeAbstractFloat } from
+'../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('min', {
+ f32: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.minInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.minInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseF64Range(),
+ sparseF64Range(),
+ 'unfiltered',
+ FP.abstract.minInterval
+ );
+ }
+});
+
+/** Generate set of min test cases from list of interesting values */
+function generateTestCases(
+values,
+makeCase)
+{
+ const cases = new Array();
+ values.forEach((e) => {
+ values.forEach((f) => {
+ cases.push(makeCase(e, f));
+ });
+ });
+ return cases;
+}
+
+g.test('abstract_int').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`abstract int tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const makeCase = (x, y) => {
+ return { input: [u32(x), u32(y)], expected: u32(Math.min(x, y)) };
+ };
+
+ const test_values = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('min'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const makeCase = (x, y) => {
+ return { input: [i32(x), i32(y)], expected: i32(Math.min(x, y)) };
+ };
+
+ const test_values = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(t, builtin('min'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('min'),
+ [TypeAbstractFloat, TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('min'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('min'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/mix.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/mix.spec.js
new file mode 100644
index 0000000000..4466ecd8a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/mix.spec.js
@@ -0,0 +1,275 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'mix' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn mix(e1: T, e2: T, e3: T) -> T
+Returns the linear blend of e1 and e2 (e.g. e1*(1-e3)+e2*e3). Component-wise when T is a vector.
+
+T is AbstractFloat, f32, or f16
+T2 is vecN<T>
+@const fn mix(e1: T2, e2: T2, e3: T) -> T2
+Returns the component-wise linear blend of e1 and e2, using scalar blending factor e3 for each component.
+Same as mix(e1,e2,T2(e3)).
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeVec, TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ sparseF32Range,
+ sparseF16Range,
+ sparseVectorF32Range,
+ sparseVectorF16Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_scalar_[non_]const
+const f32_vec_scalar_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].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])
+).
+unimplemented();
+
+g.test('abstract_float_nonmatching_vec2').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract_float tests with two vec2<abstract_float> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('abstract_float_nonmatching_vec3').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract_float tests with two vec3<abstract_float> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('abstract_float_nonmatching_vec4').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract_float tests with two vec4<abstract_float> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+unimplemented();
+
+g.test('f32_matching').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 test with matching third param`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('mix'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_nonmatching_vec2').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests with two vec2<f32> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_scalar_const' : 'f32_vec2_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_nonmatching_vec3').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests with two vec3<f32> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_scalar_const' : 'f32_vec3_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_nonmatching_vec4').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests with two vec4<f32> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_scalar_const' : 'f32_vec4_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_matching').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 test with matching third param`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('mix'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_nonmatching_vec2').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests with two vec2<f16> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_scalar_const' : 'f16_vec2_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_nonmatching_vec3').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests with two vec3<f16> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_scalar_const' : 'f16_vec3_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_nonmatching_vec4').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests with two vec4<f16> params and scalar third param`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_scalar_const' : 'f16_vec4_scalar_non_const'
+ );
+ await run(
+ t,
+ builtin('mix'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/modf.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/modf.spec.js
new file mode 100644
index 0000000000..4e3f8c0a85
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/modf.spec.js
@@ -0,0 +1,661 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'modf' builtin function
+
+T is f32 or f16 or AbstractFloat
+@const fn modf(e:T) -> result_struct
+Splits |e| into fractional and whole number parts.
+The whole part is (|e| % 1.0), and the fractional part is |e| minus the whole part.
+Returns the result_struct for the given type.
+
+S is f32 or f16 or AbstractFloat
+T is vecN<S>
+@const fn modf(e:T) -> result_struct
+Splits the components of |e| into fractional and whole number parts.
+The |i|'th component of the whole and fractional parts equal the whole and fractional parts of modf(e[i]).
+Returns the result_struct for the given type.
+
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ toVector,
+ TypeAbstractFloat,
+ TypeF16,
+ TypeF32,
+ TypeVec } from
+'../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF16Range,
+ fullF32Range,
+ fullF64Range,
+ vectorF16Range,
+ vectorF32Range,
+ vectorF64Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import {
+ abstractFloatShaderBuilder,
+ allInputSources,
+ basicExpressionBuilder,
+
+ onlyConstInputSource,
+ run } from
+
+'../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure */
+function wholeBuilder() {
+ return basicExpressionBuilder((value) => `modf(${value}).whole`);
+}
+
+/** @returns an ShaderBuilder that evaluates modf and returns .fract from the result structure */
+function fractBuilder() {
+ return basicExpressionBuilder((value) => `modf(${value}).fract`);
+}
+
+/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure for AbstractFloats */
+function abstractWholeBuilder() {
+ return abstractFloatShaderBuilder((value) => `modf(${value}).whole`);
+}
+
+/** @returns an ShaderBuilder that evaluates modf and returns .fract from the result structure for AbstractFloats */
+function abstractFractBuilder() {
+ return abstractFloatShaderBuilder((value) => `modf(${value}).fract`);
+}
+
+/** @returns a fract Case for a scalar vector input */
+function makeScalarCaseFract(kind, n) {
+ 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, n) {
+ 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, v) {
+ 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, v) {
+ 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'].
+flatMap((kind) =>
+['whole', 'fract'].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'].
+flatMap((kind) =>
+[2, 3, 4].flatMap((n) =>
+['whole', 'fract'].map((portion) => ({
+ [`${kind}_vec${n}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract;
+ return vector_range[kind][n].map(makeCase.bind(null, kind));
+ }
+}))
+)
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('modf', {
+ ...scalar_cases,
+ ...vec_cases
+});
+
+g.test('f32_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f32
+
+struct __modf_result_f32 {
+ fract : f32, // fractional part
+ whole : f32 // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_fract');
+ await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f32
+
+struct __modf_result_f32 {
+ fract : f32, // fractional part
+ whole : f32 // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_whole');
+ await run(t, wholeBuilder(), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_vec2_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f32>
+
+struct __modf_result_vec2_f32 {
+ fract : vec2<f32>, // fractional part
+ whole : vec2<f32> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec2_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f32>
+
+struct __modf_result_vec2_f32 {
+ fract : vec2<f32>, // fractional part
+ whole : vec2<f32> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec2_whole');
+ await run(t, wholeBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec3_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f32>
+
+struct __modf_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ whole : vec3<f32> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec3_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f32>
+
+struct __modf_result_vec3_f32 {
+ fract : vec3<f32>, // fractional part
+ whole : vec3<f32> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec3_whole');
+ await run(t, wholeBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec4_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f32>
+
+struct __modf_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ whole : vec4<f32> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec4_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f32>
+
+struct __modf_result_vec4_f32 {
+ fract : vec4<f32>, // fractional part
+ whole : vec4<f32> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get('f32_vec4_whole');
+ await run(t, wholeBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+});
+
+g.test('f16_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f16
+
+struct __modf_result_f16 {
+ fract : f16, // fractional part
+ whole : f16 // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_fract');
+ await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is f16
+
+struct __modf_result_f16 {
+ fract : f16, // fractional part
+ whole : f16 // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_whole');
+ await run(t, wholeBuilder(), [TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_vec2_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f16>
+
+struct __modf_result_vec2_f16 {
+ fract : vec2<f16>, // fractional part
+ whole : vec2<f16> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec2_fract');
+ await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec2_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<f16>
+
+struct __modf_result_vec2_f16 {
+ fract : vec2<f16>, // fractional part
+ whole : vec2<f16> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec2_whole');
+ await run(t, wholeBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec3_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f16>
+
+struct __modf_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ whole : vec3<f16> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec3_fract');
+ await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec3_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<f16>
+
+struct __modf_result_vec3_f16 {
+ fract : vec3<f16>, // fractional part
+ whole : vec3<f16> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec3_whole');
+ await run(t, wholeBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec4_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f16>
+
+struct __modf_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ whole : vec4<f16> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec4_fract');
+ await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec4_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<f16>
+
+struct __modf_result_vec4_f16 {
+ fract : vec4<f16>, // fractional part
+ whole : vec4<f16> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16_vec4_whole');
+ await run(t, wholeBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+});
+
+g.test('abstract_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is AbstractFloat
+
+struct __modf_result_abstract {
+ fract : AbstractFloat, // fractional part
+ whole : AbstractFloat // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_fract');
+ await run(t, abstractFractBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+});
+
+g.test('abstract_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is AbstractFloat
+
+struct __modf_result_abstract {
+ fract : AbstractFloat, // fractional part
+ whole : AbstractFloat // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_whole');
+ await run(t, abstractWholeBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+});
+
+g.test('abstract_vec2_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<abstract>
+
+struct __modf_result_vec2_abstract {
+ fract : vec2<abstract>, // fractional part
+ whole : vec2<abstract> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_vec2_fract');
+ await run(
+ t,
+ abstractFractBuilder(),
+ [TypeVec(2, TypeAbstractFloat)],
+ TypeVec(2, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('abstract_vec2_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec2<abstract>
+
+struct __modf_result_vec2_abstract {
+ fract : vec2<abstract>, // fractional part
+ whole : vec2<abstract> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_vec2_whole');
+ await run(
+ t,
+ abstractWholeBuilder(),
+ [TypeVec(2, TypeAbstractFloat)],
+ TypeVec(2, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('abstract_vec3_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<abstract>
+
+struct __modf_result_vec3_abstract {
+ fract : vec3<abstract>, // fractional part
+ whole : vec3<abstract> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_vec3_fract');
+ await run(
+ t,
+ abstractFractBuilder(),
+ [TypeVec(3, TypeAbstractFloat)],
+ TypeVec(3, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('abstract_vec3_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec3<abstract>
+
+struct __modf_result_vec3_abstract {
+ fract : vec3<abstract>, // fractional part
+ whole : vec3<abstract> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_vec3_whole');
+ await run(
+ t,
+ abstractWholeBuilder(),
+ [TypeVec(3, TypeAbstractFloat)],
+ TypeVec(3, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('abstract_vec4_fract').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<abstract>
+
+struct __modf_result_vec4_abstract {
+ fract : vec4<abstract>, // fractional part
+ whole : vec4<abstract> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_vec4_fract');
+ await run(
+ t,
+ abstractFractBuilder(),
+ [TypeVec(4, TypeAbstractFloat)],
+ TypeVec(4, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('abstract_vec4_whole').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+T is vec4<abstract>
+
+struct __modf_result_vec4_abstract {
+ fract : vec4<abstract>, // fractional part
+ whole : vec4<abstract> // whole part
+}
+`
+).
+params((u) => u.combine('inputSource', onlyConstInputSource)).
+fn(async (t) => {
+ const cases = await d.get('abstract_vec4_whole');
+ await run(
+ t,
+ abstractWholeBuilder(),
+ [TypeVec(4, TypeAbstractFloat)],
+ TypeVec(4, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/normalize.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/normalize.spec.js
new file mode 100644
index 0000000000..3aa17a8ade
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/normalize.spec.js
@@ -0,0 +1,137 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'normalize' builtin function
+
+T is AbstractFloat, f32, or f16
+@const fn normalize(e: vecN<T> ) -> vecN<T>
+Returns a unit vector in the same direction as e.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { vectorF32Range, vectorF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].map((nonConst) => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorToVectorCases(
+ vectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.normalizeInterval
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('normalize', {
+ ...f32_vec_cases,
+ ...f16_vec_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(t, builtin('normalize'), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.js
new file mode 100644
index 0000000000..1d991dc4f0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.js
@@ -0,0 +1,88 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Converts two floating point values to half-precision floating point numbers, and then combines them into one u32 value.
+Component e[i] of the input is converted to a IEEE-754 binary16 value,
+which is then placed in bits 16 × i through 16 × i + 15 of the result.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf, skipUndefined } from '../../../../../util/compare.js';
+import {
+ f32,
+ pack2x16float,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec2 } from
+'../../../../../util/conversion.js';
+import { cartesianProduct, fullF32Range, quantizeToF32 } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// pack2x16float has somewhat unusual behaviour, specifically around how it is
+// supposed to behave when values go OOB and when they are considered to have
+// gone OOB, so has its own bespoke implementation.
+
+/**
+ * @returns a Case for `pack2x16float`
+ * @param param0 first param for the case
+ * @param param1 second param for the case
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function makeCase(param0, param1, filter_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, param1s, filter_undefined) {
+ return cartesianProduct(param0s, param1s).
+ map((e) => makeCase(e[0], e[1], filter_undefined)).
+ filter((c) => c !== undefined);
+}
+
+export const d = makeCaseCache('pack2x16float', {
+ f32_const: () => {
+ return generateCases(fullF32Range(), fullF32Range(), true);
+ },
+ f32_non_const: () => {
+ return generateCases(fullF32Range(), fullF32Range(), false);
+ }
+});
+
+g.test('pack').
+specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions').
+desc(
+ `
+@const fn pack2x16float(e: vec2<f32>) -> u32
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('pack2x16float'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.js
new file mode 100644
index 0000000000..fb1ea8a5eb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.js
@@ -0,0 +1,55 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Converts two normalized floating point values to 16-bit signed integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to a 16-bit twos complement integer value
+⌊ 0.5 + 32767 × min(1, max(-1, e[i])) ⌋ which is then placed in
+bits 16 × i through 16 × i + 15 of the result.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack2x16snorm,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec2 } from
+'../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack').
+specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions').
+desc(
+ `
+@const fn pack2x16snorm(e: vec2<f32>) -> u32
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const makeCase = (x, y) => {
+ x = quantizeToF32(x);
+ y = quantizeToF32(y);
+ return { input: [vec2(f32(x), f32(y))], expected: u32(pack2x16snorm(x, y)) };
+ };
+
+ // Returns a value normalized to [-1, 1].
+ const normalizeF32 = (n) => {
+ return n / kValue.f32.positive.max;
+ };
+
+ const cases = vectorF32Range(2).flatMap((v) => {
+ return [
+ makeCase(...v),
+ makeCase(...v.map(normalizeF32))];
+
+ });
+
+ await run(t, builtin('pack2x16snorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.js
new file mode 100644
index 0000000000..cce847339d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.js
@@ -0,0 +1,55 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Converts two normalized floating point values to 16-bit unsigned integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to a 16-bit unsigned integer value
+⌊ 0.5 + 65535 × min(1, max(0, e[i])) ⌋ which is then placed in
+bits 16 × i through 16 × i + 15 of the result.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack2x16unorm,
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec2 } from
+'../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack').
+specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions').
+desc(
+ `
+@const fn pack2x16unorm(e: vec2<f32>) -> u32
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const makeCase = (x, y) => {
+ x = quantizeToF32(x);
+ y = quantizeToF32(y);
+ return { input: [vec2(f32(x), f32(y))], expected: u32(pack2x16unorm(x, y)) };
+ };
+
+ // Returns a value normalized to [0, 1].
+ const normalizeF32 = (n) => {
+ return n > 0 ? n / kValue.f32.positive.max : n / kValue.f32.negative.min;
+ };
+
+ const cases = vectorF32Range(2).flatMap((v) => {
+ return [
+ makeCase(...v),
+ makeCase(...v.map(normalizeF32))];
+
+ });
+
+ await run(t, builtin('pack2x16unorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.js
new file mode 100644
index 0000000000..60dc348348
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.js
@@ -0,0 +1,60 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Converts four normalized floating point values to 8-bit signed integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to an 8-bit twos complement integer value
+⌊ 0.5 + 127 × min(1, max(-1, e[i])) ⌋ which is then placed in
+bits 8 × i through 8 × i + 7 of the result.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack4x8snorm,
+
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec4 } from
+'../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack').
+specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions').
+desc(
+ `
+@const fn pack4x8snorm(e: vec4<f32>) -> u32
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const makeCase = (vals) => {
+ const vals_f32 = new Array(4);
+ for (const idx in vals) {
+ vals[idx] = quantizeToF32(vals[idx]);
+ vals_f32[idx] = f32(vals[idx]);
+ }
+
+ return { input: [vec4(...vals_f32)], expected: u32(pack4x8snorm(...vals)) };
+ };
+
+ // Returns a value normalized to [-1, 1].
+ const normalizeF32 = (n) => {
+ return n / kValue.f32.positive.max;
+ };
+
+ const cases = vectorF32Range(4).flatMap((v) => {
+ return [
+ makeCase(v),
+ makeCase(v.map(normalizeF32))];
+
+ });
+
+ await run(t, builtin('pack4x8snorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.js
new file mode 100644
index 0000000000..5ef35db626
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.js
@@ -0,0 +1,60 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Converts four normalized floating point values to 8-bit unsigned integers, and then combines them into one u32 value.
+Component e[i] of the input is converted to an 8-bit unsigned integer value
+⌊ 0.5 + 255 × min(1, max(0, e[i])) ⌋ which is then placed in
+bits 8 × i through 8 × i + 7 of the result.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import {
+ f32,
+ pack4x8unorm,
+
+ TypeF32,
+ TypeU32,
+ TypeVec,
+ u32,
+ vec4 } from
+'../../../../../util/conversion.js';
+import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('pack').
+specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions').
+desc(
+ `
+@const fn pack4x8unorm(e: vec4<f32>) -> u32
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const makeCase = (vals) => {
+ const vals_f32 = new Array(4);
+ for (const idx in vals) {
+ vals[idx] = quantizeToF32(vals[idx]);
+ vals_f32[idx] = f32(vals[idx]);
+ }
+
+ return { input: [vec4(...vals_f32)], expected: u32(pack4x8unorm(...vals)) };
+ };
+
+ // Returns a value normalized to [0, 1].
+ const normalizeF32 = (n) => {
+ return n > 0 ? n / kValue.f32.positive.max : n / kValue.f32.negative.min;
+ };
+
+ const cases = vectorF32Range(4).flatMap((v) => {
+ return [
+ makeCase(v),
+ makeCase(v.map(normalizeF32))];
+
+ });
+
+ await run(t, builtin('pack4x8unorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pow.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pow.spec.js
new file mode 100644
index 0000000000..45703c3df9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/pow.spec.js
@@ -0,0 +1,88 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'pow' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn pow(e1: T ,e2: T ) -> T
+Returns e1 raised to the power e2. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('pow', {
+ f32_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'finite',
+ FP.f32.powInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ fullF32Range(),
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.powInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'finite',
+ FP.f16.powInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ fullF16Range(),
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.powInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('pow'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('pow'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.js
new file mode 100644
index 0000000000..53ccd41cfb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.js
@@ -0,0 +1,70 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'quantizeToF16' builtin function
+
+T is f32 or vecN<f32>
+@const fn quantizeToF16(e: T ) -> T
+Quantizes a 32-bit floating point value e as if e were converted to a IEEE 754
+binary16 value, and then converted back to a IEEE 754 binary32 value.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { kValue } from '../../../../../util/constants.js';
+import { TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('quantizeToF16', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...fullF16Range()],
+
+ 'finite',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.quantizeToF16Interval
+ );
+ }
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/radians.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/radians.spec.js
new file mode 100644
index 0000000000..72aea0ee9f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/radians.spec.js
@@ -0,0 +1,90 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'radians' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn radians(e1: T ) -> T
+Converts degrees to radians, approximating e1 * π / 180.
+Component-wise when T is a vector
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('radians', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ fullF32Range(),
+ 'unfiltered',
+ FP.f32.radiansInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.f16.radiansInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF16Range(),
+ 'unfiltered',
+ FP.abstract.radiansInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('radians'),
+ [TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('radians'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('radians'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reflect.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reflect.spec.js
new file mode 100644
index 0000000000..0c3933ce2e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reflect.spec.js
@@ -0,0 +1,180 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'reflect' builtin function
+
+T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+@const fn reflect(e1: T, e2: T ) -> T
+For the incident vector e1 and surface orientation e2, returns the reflection
+direction e1-2*dot(e2,e1)*e2.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseVectorF32Range, sparseVectorF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].map((nonConst) => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorPairToVectorCases(
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.reflectInterval
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('reflect', {
+ ...f32_vec_cases,
+ ...f16_vec_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+unimplemented();
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('reflect'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/refract.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/refract.spec.js
new file mode 100644
index 0000000000..53ccfb4fed
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/refract.spec.js
@@ -0,0 +1,253 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'refract' builtin function
+
+T is vecN<I>
+I is AbstractFloat, f32, or f16
+@const fn refract(e1: T ,e2: T ,e3: I ) -> T
+For the incident vector e1 and surface normal e2, and the ratio of indices of
+refraction e3, let k = 1.0 -e3*e3* (1.0 - dot(e2,e1) * dot(e2,e1)).
+If k < 0.0, returns the refraction vector 0.0, otherwise return the refraction
+vector e3*e1- (e3* dot(e2,e1) + sqrt(k)) *e2.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+
+import { GPUTest } from '../../../../../gpu_test.js';
+import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ sparseVectorF32Range,
+ sparseVectorF16Range,
+ sparseF32Range,
+ sparseF16Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// 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,
+i,
+s,
+r,
+check)
+{
+ 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,
+param_is,
+param_ss,
+param_rs,
+check)
+{
+ // 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 !== undefined);
+}
+
+// Cases: f32_vecN_[non_]const
+const f32_vec_cases = [2, 3, 4].
+flatMap((n) =>
+[true, false].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].
+flatMap((n) =>
+[true, false].map((nonConst) => ({
+ [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return generateCases(
+ 'f16',
+ sparseVectorF16Range(n),
+ sparseVectorF16Range(n),
+ sparseF16Range(),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('refract', {
+ ...f32_vec_cases,
+ ...f16_vec_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4])).
+unimplemented();
+
+g.test('f32_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
+ TypeVec(2, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
+ TypeVec(3, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f32 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
+ TypeVec(4, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec2').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec2s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
+ TypeVec(2, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec3').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec3s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
+ TypeVec(3, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16_vec4').
+specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions').
+desc(`f16 tests using vec4s`).
+params((u) => u.combine('inputSource', allInputSources)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
+ );
+ await run(
+ t,
+ builtin('refract'),
+ [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
+ TypeVec(4, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.js
new file mode 100644
index 0000000000..fc99867f10
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.js
@@ -0,0 +1,250 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'reversBits' builtin function
+
+S is i32, u32
+T is S or vecN<S>
+@const fn reverseBits(e: T ) -> T
+Reverses the bits in e: The bit at position k of the result equals the bit at position 31-k of e.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeU32, u32Bits, TypeI32, i32Bits } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions').
+desc(`u32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+
+ await run(t, builtin('reverseBits'), [TypeU32], TypeU32, cfg, [
+ // Zero
+ { input: u32Bits(0b00000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000000) },
+
+ // One
+ { input: u32Bits(0b00000000000000000000000000000001), expected: u32Bits(0b10000000000000000000000000000000) },
+
+ // 0's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000010), expected: u32Bits(0b01000000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000000100), expected: u32Bits(0b00100000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000001000), expected: u32Bits(0b00010000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000010000), expected: u32Bits(0b00001000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000100000), expected: u32Bits(0b00000100000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000001000000), expected: u32Bits(0b00000010000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000010000000), expected: u32Bits(0b00000001000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000100000000), expected: u32Bits(0b00000000100000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000001000000000), expected: u32Bits(0b00000000010000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000010000000000), expected: u32Bits(0b00000000001000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000100000000000), expected: u32Bits(0b00000000000100000000000000000000) },
+ { input: u32Bits(0b00000000000000000001000000000000), expected: u32Bits(0b00000000000010000000000000000000) },
+ { input: u32Bits(0b00000000000000000010000000000000), expected: u32Bits(0b00000000000001000000000000000000) },
+ { input: u32Bits(0b00000000000000000100000000000000), expected: u32Bits(0b00000000000000100000000000000000) },
+ { input: u32Bits(0b00000000000000001000000000000000), expected: u32Bits(0b00000000000000010000000000000000) },
+ { input: u32Bits(0b00000000000000010000000000000000), expected: u32Bits(0b00000000000000001000000000000000) },
+ { input: u32Bits(0b00000000000000100000000000000000), expected: u32Bits(0b00000000000000000100000000000000) },
+ { input: u32Bits(0b00000000000001000000000000000000), expected: u32Bits(0b00000000000000000010000000000000) },
+ { input: u32Bits(0b00000000000010000000000000000000), expected: u32Bits(0b00000000000000000001000000000000) },
+ { input: u32Bits(0b00000000000100000000000000000000), expected: u32Bits(0b00000000000000000000100000000000) },
+ { input: u32Bits(0b00000000001000000000000000000000), expected: u32Bits(0b00000000000000000000010000000000) },
+ { input: u32Bits(0b00000000010000000000000000000000), expected: u32Bits(0b00000000000000000000001000000000) },
+ { input: u32Bits(0b00000000100000000000000000000000), expected: u32Bits(0b00000000000000000000000100000000) },
+ { input: u32Bits(0b00000001000000000000000000000000), expected: u32Bits(0b00000000000000000000000010000000) },
+ { input: u32Bits(0b00000010000000000000000000000000), expected: u32Bits(0b00000000000000000000000001000000) },
+ { input: u32Bits(0b00000100000000000000000000000000), expected: u32Bits(0b00000000000000000000000000100000) },
+ { input: u32Bits(0b00001000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000010000) },
+ { input: u32Bits(0b00010000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000001000) },
+ { input: u32Bits(0b00100000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000100) },
+ { input: u32Bits(0b01000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000010) },
+ { input: u32Bits(0b10000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000001) },
+
+ // 1's after leading 1
+ { input: u32Bits(0b00000000000000000000000000000011), expected: u32Bits(0b11000000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000000111), expected: u32Bits(0b11100000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000001111), expected: u32Bits(0b11110000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000011111), expected: u32Bits(0b11111000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000111111), expected: u32Bits(0b11111100000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000001111111), expected: u32Bits(0b11111110000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32Bits(0b11111111000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000111111111), expected: u32Bits(0b11111111100000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32Bits(0b11111111110000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000011111111111), expected: u32Bits(0b11111111111000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000111111111111), expected: u32Bits(0b11111111111100000000000000000000) },
+ { input: u32Bits(0b00000000000000000001111111111111), expected: u32Bits(0b11111111111110000000000000000000) },
+ { input: u32Bits(0b00000000000000000011111111111111), expected: u32Bits(0b11111111111111000000000000000000) },
+ { input: u32Bits(0b00000000000000000111111111111111), expected: u32Bits(0b11111111111111100000000000000000) },
+ { input: u32Bits(0b00000000000000001111111111111111), expected: u32Bits(0b11111111111111110000000000000000) },
+ { input: u32Bits(0b00000000000000011111111111111111), expected: u32Bits(0b11111111111111111000000000000000) },
+ { input: u32Bits(0b00000000000000111111111111111111), expected: u32Bits(0b11111111111111111100000000000000) },
+ { input: u32Bits(0b00000000000001111111111111111111), expected: u32Bits(0b11111111111111111110000000000000) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32Bits(0b11111111111111111111000000000000) },
+ { input: u32Bits(0b00000000000111111111111111111111), expected: u32Bits(0b11111111111111111111100000000000) },
+ { input: u32Bits(0b00000000001111111111111111111111), expected: u32Bits(0b11111111111111111111110000000000) },
+ { input: u32Bits(0b00000000011111111111111111111111), expected: u32Bits(0b11111111111111111111111000000000) },
+ { input: u32Bits(0b00000000111111111111111111111111), expected: u32Bits(0b11111111111111111111111100000000) },
+ { input: u32Bits(0b00000001111111111111111111111111), expected: u32Bits(0b11111111111111111111111110000000) },
+ { input: u32Bits(0b00000011111111111111111111111111), expected: u32Bits(0b11111111111111111111111111000000) },
+ { input: u32Bits(0b00000111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111100000) },
+ { input: u32Bits(0b00001111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111110000) },
+ { input: u32Bits(0b00011111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111000) },
+ { input: u32Bits(0b00111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111100) },
+ { input: u32Bits(0b01111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111110) },
+ { input: u32Bits(0b11111111111111111111111111111111), expected: u32Bits(0b11111111111111111111111111111111) },
+
+ // random after leading 1
+ { input: u32Bits(0b00000000000000000000000000000110), expected: u32Bits(0b01100000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000001101), expected: u32Bits(0b10110000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000011101), expected: u32Bits(0b10111000000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000000111001), expected: u32Bits(0b10011100000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000001101111), expected: u32Bits(0b11110110000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000011111111), expected: u32Bits(0b11111111000000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000000111101111), expected: u32Bits(0b11110111100000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000001111111111), expected: u32Bits(0b11111111110000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000011111110001), expected: u32Bits(0b10001111111000000000000000000000) },
+ { input: u32Bits(0b00000000000000000000111011011101), expected: u32Bits(0b10111011011100000000000000000000) },
+ { input: u32Bits(0b00000000000000000001101101111111), expected: u32Bits(0b11111110110110000000000000000000) },
+ { input: u32Bits(0b00000000000000000011111111011111), expected: u32Bits(0b11111011111111000000000000000000) },
+ { input: u32Bits(0b00000000000000000101111001110101), expected: u32Bits(0b10101110011110100000000000000000) },
+ { input: u32Bits(0b00000000000000001101111011110111), expected: u32Bits(0b11101111011110110000000000000000) },
+ { input: u32Bits(0b00000000000000011111111111110011), expected: u32Bits(0b11001111111111111000000000000000) },
+ { input: u32Bits(0b00000000000000111111111110111111), expected: u32Bits(0b11111101111111111100000000000000) },
+ { input: u32Bits(0b00000000000001111111011111111111), expected: u32Bits(0b11111111111011111110000000000000) },
+ { input: u32Bits(0b00000000000011111111111111111111), expected: u32Bits(0b11111111111111111111000000000000) },
+ { input: u32Bits(0b00000000000111110101011110111111), expected: u32Bits(0b11111101111010101111100000000000) },
+ { input: u32Bits(0b00000000001111101111111111110111), expected: u32Bits(0b11101111111111110111110000000000) },
+ { input: u32Bits(0b00000000011111111111010000101111), expected: u32Bits(0b11110100001011111111111000000000) },
+ { input: u32Bits(0b00000000111111111111001111111011), expected: u32Bits(0b11011111110011111111111100000000) },
+ { input: u32Bits(0b00000001111111011111101111111111), expected: u32Bits(0b11111111110111111011111110000000) },
+ { input: u32Bits(0b00000011101011111011110111111011), expected: u32Bits(0b11011111101111011111010111000000) },
+ { input: u32Bits(0b00000111111110111111111111111111), expected: u32Bits(0b11111111111111111101111111100000) },
+ { input: u32Bits(0b00001111000000011011011110111111), expected: u32Bits(0b11111101111011011000000011110000) },
+ { input: u32Bits(0b00011110101111011111111111111111), expected: u32Bits(0b11111111111111111011110101111000) },
+ { input: u32Bits(0b00110110111111100111111110111101), expected: u32Bits(0b10111101111111100111111101101100) },
+ { input: u32Bits(0b01010111111101111111011111011111), expected: u32Bits(0b11111011111011111110111111101010) },
+ { input: u32Bits(0b11100010011110101101101110101111), expected: u32Bits(0b11110101110110110101111001000111) }]
+ );
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/2021/WD-WGSL-20210929/#integer-builtin-functions').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cfg = t.params;
+
+ await run(t, builtin('reverseBits'), [TypeI32], TypeI32, cfg, [
+ // Zero
+ { input: i32Bits(0b00000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000000) },
+
+ // One
+ { input: i32Bits(0b00000000000000000000000000000001), expected: i32Bits(0b10000000000000000000000000000000) },
+
+ // 0's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000010), expected: i32Bits(0b01000000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000000100), expected: i32Bits(0b00100000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000001000), expected: i32Bits(0b00010000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000010000), expected: i32Bits(0b00001000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000100000), expected: i32Bits(0b00000100000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000001000000), expected: i32Bits(0b00000010000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000010000000), expected: i32Bits(0b00000001000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000100000000), expected: i32Bits(0b00000000100000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000001000000000), expected: i32Bits(0b00000000010000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000010000000000), expected: i32Bits(0b00000000001000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000100000000000), expected: i32Bits(0b00000000000100000000000000000000) },
+ { input: i32Bits(0b00000000000000000001000000000000), expected: i32Bits(0b00000000000010000000000000000000) },
+ { input: i32Bits(0b00000000000000000010000000000000), expected: i32Bits(0b00000000000001000000000000000000) },
+ { input: i32Bits(0b00000000000000000100000000000000), expected: i32Bits(0b00000000000000100000000000000000) },
+ { input: i32Bits(0b00000000000000001000000000000000), expected: i32Bits(0b00000000000000010000000000000000) },
+ { input: i32Bits(0b00000000000000010000000000000000), expected: i32Bits(0b00000000000000001000000000000000) },
+ { input: i32Bits(0b00000000000000100000000000000000), expected: i32Bits(0b00000000000000000100000000000000) },
+ { input: i32Bits(0b00000000000001000000000000000000), expected: i32Bits(0b00000000000000000010000000000000) },
+ { input: i32Bits(0b00000000000010000000000000000000), expected: i32Bits(0b00000000000000000001000000000000) },
+ { input: i32Bits(0b00000000000100000000000000000000), expected: i32Bits(0b00000000000000000000100000000000) },
+ { input: i32Bits(0b00000000001000000000000000000000), expected: i32Bits(0b00000000000000000000010000000000) },
+ { input: i32Bits(0b00000000010000000000000000000000), expected: i32Bits(0b00000000000000000000001000000000) },
+ { input: i32Bits(0b00000000100000000000000000000000), expected: i32Bits(0b00000000000000000000000100000000) },
+ { input: i32Bits(0b00000001000000000000000000000000), expected: i32Bits(0b00000000000000000000000010000000) },
+ { input: i32Bits(0b00000010000000000000000000000000), expected: i32Bits(0b00000000000000000000000001000000) },
+ { input: i32Bits(0b00000100000000000000000000000000), expected: i32Bits(0b00000000000000000000000000100000) },
+ { input: i32Bits(0b00001000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000010000) },
+ { input: i32Bits(0b00010000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000001000) },
+ { input: i32Bits(0b00100000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000100) },
+ { input: i32Bits(0b01000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000010) },
+ { input: i32Bits(0b10000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000001) },
+
+ // 1's after leading 1
+ { input: i32Bits(0b00000000000000000000000000000011), expected: i32Bits(0b11000000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000000111), expected: i32Bits(0b11100000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000001111), expected: i32Bits(0b11110000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000011111), expected: i32Bits(0b11111000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000111111), expected: i32Bits(0b11111100000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000001111111), expected: i32Bits(0b11111110000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32Bits(0b11111111000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000111111111), expected: i32Bits(0b11111111100000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32Bits(0b11111111110000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000011111111111), expected: i32Bits(0b11111111111000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000111111111111), expected: i32Bits(0b11111111111100000000000000000000) },
+ { input: i32Bits(0b00000000000000000001111111111111), expected: i32Bits(0b11111111111110000000000000000000) },
+ { input: i32Bits(0b00000000000000000011111111111111), expected: i32Bits(0b11111111111111000000000000000000) },
+ { input: i32Bits(0b00000000000000000111111111111111), expected: i32Bits(0b11111111111111100000000000000000) },
+ { input: i32Bits(0b00000000000000001111111111111111), expected: i32Bits(0b11111111111111110000000000000000) },
+ { input: i32Bits(0b00000000000000011111111111111111), expected: i32Bits(0b11111111111111111000000000000000) },
+ { input: i32Bits(0b00000000000000111111111111111111), expected: i32Bits(0b11111111111111111100000000000000) },
+ { input: i32Bits(0b00000000000001111111111111111111), expected: i32Bits(0b11111111111111111110000000000000) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32Bits(0b11111111111111111111000000000000) },
+ { input: i32Bits(0b00000000000111111111111111111111), expected: i32Bits(0b11111111111111111111100000000000) },
+ { input: i32Bits(0b00000000001111111111111111111111), expected: i32Bits(0b11111111111111111111110000000000) },
+ { input: i32Bits(0b00000000011111111111111111111111), expected: i32Bits(0b11111111111111111111111000000000) },
+ { input: i32Bits(0b00000000111111111111111111111111), expected: i32Bits(0b11111111111111111111111100000000) },
+ { input: i32Bits(0b00000001111111111111111111111111), expected: i32Bits(0b11111111111111111111111110000000) },
+ { input: i32Bits(0b00000011111111111111111111111111), expected: i32Bits(0b11111111111111111111111111000000) },
+ { input: i32Bits(0b00000111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111100000) },
+ { input: i32Bits(0b00001111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111110000) },
+ { input: i32Bits(0b00011111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111000) },
+ { input: i32Bits(0b00111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111100) },
+ { input: i32Bits(0b01111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111110) },
+ { input: i32Bits(0b11111111111111111111111111111111), expected: i32Bits(0b11111111111111111111111111111111) },
+
+ // random after leading 1
+ { input: i32Bits(0b00000000000000000000000000000110), expected: i32Bits(0b01100000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000001101), expected: i32Bits(0b10110000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000011101), expected: i32Bits(0b10111000000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000000111001), expected: i32Bits(0b10011100000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000001101111), expected: i32Bits(0b11110110000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000011111111), expected: i32Bits(0b11111111000000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000000111101111), expected: i32Bits(0b11110111100000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000001111111111), expected: i32Bits(0b11111111110000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000011111110001), expected: i32Bits(0b10001111111000000000000000000000) },
+ { input: i32Bits(0b00000000000000000000111011011101), expected: i32Bits(0b10111011011100000000000000000000) },
+ { input: i32Bits(0b00000000000000000001101101111111), expected: i32Bits(0b11111110110110000000000000000000) },
+ { input: i32Bits(0b00000000000000000011111111011111), expected: i32Bits(0b11111011111111000000000000000000) },
+ { input: i32Bits(0b00000000000000000101111001110101), expected: i32Bits(0b10101110011110100000000000000000) },
+ { input: i32Bits(0b00000000000000001101111011110111), expected: i32Bits(0b11101111011110110000000000000000) },
+ { input: i32Bits(0b00000000000000011111111111110011), expected: i32Bits(0b11001111111111111000000000000000) },
+ { input: i32Bits(0b00000000000000111111111110111111), expected: i32Bits(0b11111101111111111100000000000000) },
+ { input: i32Bits(0b00000000000001111111011111111111), expected: i32Bits(0b11111111111011111110000000000000) },
+ { input: i32Bits(0b00000000000011111111111111111111), expected: i32Bits(0b11111111111111111111000000000000) },
+ { input: i32Bits(0b00000000000111110101011110111111), expected: i32Bits(0b11111101111010101111100000000000) },
+ { input: i32Bits(0b00000000001111101111111111110111), expected: i32Bits(0b11101111111111110111110000000000) },
+ { input: i32Bits(0b00000000011111111111010000101111), expected: i32Bits(0b11110100001011111111111000000000) },
+ { input: i32Bits(0b00000000111111111111001111111011), expected: i32Bits(0b11011111110011111111111100000000) },
+ { input: i32Bits(0b00000001111111011111101111111111), expected: i32Bits(0b11111111110111111011111110000000) },
+ { input: i32Bits(0b00000011101011111011110111111011), expected: i32Bits(0b11011111101111011111010111000000) },
+ { input: i32Bits(0b00000111111110111111111111111111), expected: i32Bits(0b11111111111111111101111111100000) },
+ { input: i32Bits(0b00001111000000011011011110111111), expected: i32Bits(0b11111101111011011000000011110000) },
+ { input: i32Bits(0b00011110101111011111111111111111), expected: i32Bits(0b11111111111111111011110101111000) },
+ { input: i32Bits(0b00110110111111100111111110111101), expected: i32Bits(0b10111101111111100111111101101100) },
+ { input: i32Bits(0b01010111111101111111011111011111), expected: i32Bits(0b11111011111011111110111111101010) },
+ { input: i32Bits(0b11100010011110101101101110101111), expected: i32Bits(0b11110101110110110101111001000111) }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/round.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/round.spec.js
new file mode 100644
index 0000000000..d5e6eec3aa
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/round.spec.js
@@ -0,0 +1,79 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'round' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn round(e: T) -> T
+Result is the integer k nearest to e, as a floating point value.
+When e lies halfway between integers k and k+1, the result is k when k is even,
+and k+1 when k is odd.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('round', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ 0x80000000, // https://github.com/gpuweb/cts/issues/2766,
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.roundInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.roundInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('round'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('round'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/saturate.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/saturate.spec.js
new file mode 100644
index 0000000000..1869d9c058
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/saturate.spec.js
@@ -0,0 +1,100 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'saturate' builtin function
+
+S is AbstractFloat, f32, or f16
+T is S or vecN<S>
+@const fn saturate(e: T) -> T
+Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('saturate', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Non-clamped values
+ ...linearRange(0.0, 1.0, 20),
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.saturateInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Non-clamped values
+ ...linearRange(0.0, 1.0, 20),
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.saturateInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [
+ // Non-clamped values
+ ...linearRange(0.0, 1.0, 20),
+ ...fullF64Range()],
+
+ 'unfiltered',
+ FP.abstract.saturateInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractBuiltin('saturate'),
+ [TypeAbstractFloat],
+ TypeAbstractFloat,
+ t.params,
+ cases
+ );
+});
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('saturate'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('saturate'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/select.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/select.spec.js
new file mode 100644
index 0000000000..8248e04dc0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/select.spec.js
@@ -0,0 +1,253 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'select' builtin function
+
+T is scalar, abstract numeric type, or vector
+@const fn select(f: T, t: T, cond: bool) -> T
+Returns t when cond is true, and f otherwise.
+
+T is scalar or abstract numeric type
+@const fn select(f: vecN<T>, t: vecN<T>, cond: vecN<bool>) -> vecN<T>
+Component-wise selection. Result component i is evaluated as select(f[i],t[i],cond[i]).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+
+ TypeVec,
+ TypeBool,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeU32,
+ f32,
+ f16,
+ i32,
+ u32,
+ False,
+ True,
+ bool,
+ vec2,
+ vec3,
+ vec4,
+ abstractFloat,
+ TypeAbstractFloat } from
+'../../../../../util/conversion.js';
+import { run, allInputSources } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function makeBool(n) {
+ return bool((n & 1) === 1);
+}
+
+
+
+const dataType = {
+ b: {
+ type: TypeBool,
+ constructor: makeBool
+ },
+ af: {
+ type: TypeAbstractFloat,
+ constructor: abstractFloat
+ },
+ f: {
+ type: TypeF32,
+ constructor: f32
+ },
+ h: {
+ type: TypeF16,
+ constructor: f16
+ },
+ i: {
+ type: TypeI32,
+ constructor: i32
+ },
+ u: {
+ type: TypeU32,
+ constructor: u32
+ }
+};
+
+g.test('scalar').
+specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions').
+desc(`scalar tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('component', ['b', 'af', 'f', 'h', 'i', 'u']).
+combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.component === 'h') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+}).
+fn(async (t) => {
+ const componentType = dataType[t.params.component].type;
+ const cons = dataType[t.params.component].constructor;
+
+ // Create the scalar values that will be selected from, either as scalars
+ // or vectors.
+ //
+ // Each boolean will select between c[k] and c[k+4]. Those values must
+ // always compare as different. The tricky case is boolean, where the parity
+ // has to be different, i.e. c[k]-c[k+4] must be odd.
+ const c = [0, 1, 2, 3, 5, 6, 7, 8].map((i) => cons(i));
+ // Now form vectors that will have different components from each other.
+ const v2a = vec2(c[0], c[1]);
+ const v2b = vec2(c[4], c[5]);
+ const v3a = vec3(c[0], c[1], c[2]);
+ const v3b = vec3(c[4], c[5], c[6]);
+ const v4a = vec4(c[0], c[1], c[2], c[3]);
+ const v4b = vec4(c[4], c[5], c[6], c[7]);
+
+ const overloads = {
+ scalar: {
+ type: componentType,
+ cases: [
+ { input: [c[0], c[1], False], expected: c[0] },
+ { input: [c[0], c[1], True], expected: c[1] }]
+
+ },
+ vec2: {
+ type: TypeVec(2, componentType),
+ cases: [
+ { input: [v2a, v2b, False], expected: v2a },
+ { input: [v2a, v2b, True], expected: v2b }]
+
+ },
+ vec3: {
+ type: TypeVec(3, componentType),
+ cases: [
+ { input: [v3a, v3b, False], expected: v3a },
+ { input: [v3a, v3b, True], expected: v3b }]
+
+ },
+ vec4: {
+ type: TypeVec(4, componentType),
+ cases: [
+ { input: [v4a, v4b, False], expected: v4a },
+ { input: [v4a, v4b, True], expected: v4b }]
+
+ }
+ };
+ const overload = overloads[t.params.overload];
+
+ await run(
+ t,
+ t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
+ [overload.type, overload.type, TypeBool],
+ overload.type,
+ t.params,
+ overload.cases
+ );
+});
+
+g.test('vector').
+specURL('https://www.w3.org/TR/WGSL/#logical-builtin-functions').
+desc(`vector tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('component', ['b', 'af', 'f', 'h', 'i', 'u']).
+combine('overload', ['vec2', 'vec3', 'vec4'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.component === 'h') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+}).
+fn(async (t) => {
+ const componentType = dataType[t.params.component].type;
+ const cons = dataType[t.params.component].constructor;
+
+ // Create the scalar values that will be selected from.
+ //
+ // Each boolean will select between c[k] and c[k+4]. Those values must
+ // always compare as different. The tricky case is boolean, where the parity
+ // has to be different, i.e. c[k]-c[k+4] must be odd.
+ const c = [0, 1, 2, 3, 5, 6, 7, 8].map((i) => cons(i));
+ const T = True;
+ const F = False;
+
+ let tests;
+
+ switch (t.params.overload) {
+ case 'vec2':{
+ const a = vec2(c[0], c[1]);
+ const b = vec2(c[4], c[5]);
+ tests = {
+ dataType: TypeVec(2, componentType),
+ boolType: TypeVec(2, TypeBool),
+ cases: [
+ { input: [a, b, vec2(F, F)], expected: vec2(a.x, a.y) },
+ { input: [a, b, vec2(F, T)], expected: vec2(a.x, b.y) },
+ { input: [a, b, vec2(T, F)], expected: vec2(b.x, a.y) },
+ { input: [a, b, vec2(T, T)], expected: vec2(b.x, b.y) }]
+
+ };
+ break;
+ }
+ case 'vec3':{
+ const a = vec3(c[0], c[1], c[2]);
+ const b = vec3(c[4], c[5], c[6]);
+ tests = {
+ dataType: TypeVec(3, componentType),
+ boolType: TypeVec(3, TypeBool),
+ cases: [
+ { input: [a, b, vec3(F, F, F)], expected: vec3(a.x, a.y, a.z) },
+ { input: [a, b, vec3(F, F, T)], expected: vec3(a.x, a.y, b.z) },
+ { input: [a, b, vec3(F, T, F)], expected: vec3(a.x, b.y, a.z) },
+ { input: [a, b, vec3(F, T, T)], expected: vec3(a.x, b.y, b.z) },
+ { input: [a, b, vec3(T, F, F)], expected: vec3(b.x, a.y, a.z) },
+ { input: [a, b, vec3(T, F, T)], expected: vec3(b.x, a.y, b.z) },
+ { input: [a, b, vec3(T, T, F)], expected: vec3(b.x, b.y, a.z) },
+ { input: [a, b, vec3(T, T, T)], expected: vec3(b.x, b.y, b.z) }]
+
+ };
+ break;
+ }
+ case 'vec4':{
+ const a = vec4(c[0], c[1], c[2], c[3]);
+ const b = vec4(c[4], c[5], c[6], c[7]);
+ tests = {
+ dataType: TypeVec(4, componentType),
+ boolType: TypeVec(4, TypeBool),
+ cases: [
+ { input: [a, b, vec4(F, F, F, F)], expected: vec4(a.x, a.y, a.z, a.w) },
+ { input: [a, b, vec4(F, F, F, T)], expected: vec4(a.x, a.y, a.z, b.w) },
+ { input: [a, b, vec4(F, F, T, F)], expected: vec4(a.x, a.y, b.z, a.w) },
+ { input: [a, b, vec4(F, F, T, T)], expected: vec4(a.x, a.y, b.z, b.w) },
+ { input: [a, b, vec4(F, T, F, F)], expected: vec4(a.x, b.y, a.z, a.w) },
+ { input: [a, b, vec4(F, T, F, T)], expected: vec4(a.x, b.y, a.z, b.w) },
+ { input: [a, b, vec4(F, T, T, F)], expected: vec4(a.x, b.y, b.z, a.w) },
+ { input: [a, b, vec4(F, T, T, T)], expected: vec4(a.x, b.y, b.z, b.w) },
+ { input: [a, b, vec4(T, F, F, F)], expected: vec4(b.x, a.y, a.z, a.w) },
+ { input: [a, b, vec4(T, F, F, T)], expected: vec4(b.x, a.y, a.z, b.w) },
+ { input: [a, b, vec4(T, F, T, F)], expected: vec4(b.x, a.y, b.z, a.w) },
+ { input: [a, b, vec4(T, F, T, T)], expected: vec4(b.x, a.y, b.z, b.w) },
+ { input: [a, b, vec4(T, T, F, F)], expected: vec4(b.x, b.y, a.z, a.w) },
+ { input: [a, b, vec4(T, T, F, T)], expected: vec4(b.x, b.y, a.z, b.w) },
+ { input: [a, b, vec4(T, T, T, F)], expected: vec4(b.x, b.y, b.z, a.w) },
+ { input: [a, b, vec4(T, T, T, T)], expected: vec4(b.x, b.y, b.z, b.w) }]
+
+ };
+ break;
+ }
+ }
+
+ await run(
+ t,
+ t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
+ [tests.dataType, tests.dataType, tests.boolType],
+ tests.dataType,
+ t.params,
+ tests.cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sign.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sign.spec.js
new file mode 100644
index 0000000000..5bbc1527e3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sign.spec.js
@@ -0,0 +1,109 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'sign' builtin function
+
+S is AbstractFloat, AbstractInt, i32, f32, f16
+T is S or vecN<S>
+@const fn sign(e: T ) -> T
+Returns the sign of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ i32,
+ TypeF32,
+ TypeF16,
+ TypeI32,
+ TypeAbstractFloat } from
+'../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ fullF32Range,
+ fullF16Range,
+ fullI32Range,
+ fullF64Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sign', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.signInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.signInterval);
+ },
+ abstract_float: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'unfiltered',
+ FP.abstract.signInterval
+ );
+ },
+ i32: () =>
+ fullI32Range().map((i) => {
+ const signFunc = (i) => i < 0 ? -1 : i > 0 ? 1 : 0;
+ return { input: [i32(i)], expected: i32(signFunc(i)) };
+ })
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#sign-builtin').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract_float');
+ await run(t, abstractBuiltin('sign'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+});
+
+g.test('abstract_int').
+specURL('https://www.w3.org/TR/WGSL/#sign-builtin').
+desc(`abstract int tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#sign-builtin').
+desc(`i32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('i32');
+ await run(t, builtin('sign'), [TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#sign-builtin').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('sign'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#sign-builtin').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('sign'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sin.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sin.spec.js
new file mode 100644
index 0000000000..776ba690a0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sin.spec.js
@@ -0,0 +1,84 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'sin' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn sin(e: T ) -> T
+Returns the sine of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sin', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.sinInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 1000),
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.sinInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(
+ `
+f32 tests
+
+TODO(#792): Decide what the ground-truth is for these tests. [1]
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('sin'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('sin'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sinh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sinh.spec.js
new file mode 100644
index 0000000000..a691680867
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sinh.spec.js
@@ -0,0 +1,68 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'sinh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn sinh(e: T ) -> T
+Returns the hyperbolic sine of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sinh', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sinhInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sinhInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sinhInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sinhInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('sinh'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('sinh'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.js
new file mode 100644
index 0000000000..5a5f113ab8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.js
@@ -0,0 +1,94 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'smoothstep' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn smoothstep(low: T , high: T , x: T ) -> T
+Returns the smooth Hermite interpolation between 0 and 1.
+Component-wise when T is a vector.
+For scalar T, the result is t * t * (3.0 - 2.0 * t), where t = clamp((x - low) / (high - low), 0.0, 1.0).
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('smoothstep', {
+ f32_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'finite',
+ FP.f32.smoothStepInterval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarTripleToIntervalCases(
+ sparseF32Range(),
+ sparseF32Range(),
+ sparseF32Range(),
+ 'unfiltered',
+ FP.f32.smoothStepInterval
+ );
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'finite',
+ FP.f16.smoothStepInterval
+ );
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarTripleToIntervalCases(
+ sparseF16Range(),
+ sparseF16Range(),
+ sparseF16Range(),
+ 'unfiltered',
+ FP.f16.smoothStepInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('smoothstep'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('smoothstep'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sqrt.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sqrt.spec.js
new file mode 100644
index 0000000000..488db327d7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/sqrt.spec.js
@@ -0,0 +1,68 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'sqrt' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn sqrt(e: T ) -> T
+Returns the square root of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('sqrt', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sqrtInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sqrtInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sqrtInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sqrtInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
+ await run(t, builtin('sqrt'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
+ await run(t, builtin('sqrt'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/step.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/step.spec.js
new file mode 100644
index 0000000000..e11f25505d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/step.spec.js
@@ -0,0 +1,87 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'step' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn step(edge: T ,x: T ) -> T
+Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// stepInterval's return value can't always be interpreted as a single acceptance
+// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a
+// value in interval (0.0, 1.0).
+// See the comment block on stepInterval for more details
+const makeCase = (trait, edge, x) => {
+ 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])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('step'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('step'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.js
new file mode 100644
index 0000000000..f5f305c118
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/storageBarrier.spec.js
@@ -0,0 +1,38 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+'storageBarrier' affects memory and atomic operations in the storage address space.
+
+All synchronization functions execute a control barrier with Acquire/Release memory ordering.
+That is, all synchronization functions, and affected memory and atomic operations are ordered
+in program order relative to the synchronization function. Additionally, the affected memory
+and atomic operations program-ordered before the synchronization function must be visible to
+all other threads in the workgroup before any affected memory or atomic operation program-ordered
+after the synchronization function is executed by a member of the workgroup. All synchronization
+functions use the Workgroup memory scope. All synchronization functions have a Workgroup
+execution scope.
+
+All synchronization functions must only be used in the compute shader stage.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions').
+desc(
+ `
+All synchronization functions must only be used in the compute shader stage.
+`
+).
+params((u) => u.combine('stage', ['vertex', 'fragment', 'compute'])).
+unimplemented();
+
+g.test('barrier').
+specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions').
+desc(
+ `
+fn storageBarrier()
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tan.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tan.spec.js
new file mode 100644
index 0000000000..72fa92f5af
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tan.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'tan' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn tan(e: T ) -> T
+Returns the tangent of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('tan', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // Defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...fullF32Range()],
+
+ 'unfiltered',
+ FP.f32.tanInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // Defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...fullF16Range()],
+
+ 'unfiltered',
+ FP.f16.tanInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('tan'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('tan'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tanh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tanh.spec.js
new file mode 100644
index 0000000000..41498f4318
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/tanh.spec.js
@@ -0,0 +1,62 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'tanh' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn tanh(e: T ) -> T
+Returns the hyperbolic tangent of e. Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('tanh', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.tanhInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.tanhInterval);
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented();
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('tanh'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('tanh'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.js
new file mode 100644
index 0000000000..8ed1e289ee
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.js
@@ -0,0 +1,160 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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']
+).
+beginSubcases().
+combine('sampled_type', ['f32-only', 'i32', 'u32']).
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+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'])
+).
+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']
+).
+beginSubcases().
+combine('access_mode', ['read', 'write', 'read_write'])
+).
+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(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGather.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGather.spec.js
new file mode 100644
index 0000000000..162832708f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGather.spec.js
@@ -0,0 +1,270 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureGather' builtin function
+
+A texture gather operation reads from a 2D, 2D array, cube, or cube array texture, computing a four-component vector as follows:
+ * Find the four texels that would be used in a sampling operation with linear filtering, from mip level 0:
+ - Use the specified coordinate, array index (when present), and offset (when present).
+ - The texels are adjacent, forming a square, when considering their texture space coordinates (u,v).
+ - Selected texels at the texture edge, cube face edge, or cube corners are handled as in ordinary texture sampling.
+ * For each texel, read one channel and convert it into a scalar value.
+ - For non-depth textures, a zero-based component parameter specifies the channel to use.
+ * If the texture format supports the specified channel, i.e. has more than component channels:
+ - Yield scalar value v[component] when the texel value is v.
+ * Otherwise:
+ - Yield 0.0 when component is 1 or 2.
+ - Yield 1.0 when component is 3 (the alpha channel).
+ - For depth textures, yield the texel value. (Depth textures only have one channel.)
+ * Yield the four-component vector, arranging scalars produced by the previous step into components according to the relative coordinates of the texels, as follows:
+ - Result component Relative texel coordinate
+ x (umin,vmax)
+ y (umax,vmax)
+ z (umax,vmin)
+ w (umin,vmin)
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_2d<T>, s: sampler, coords: vec2<f32>) -> vec4<T>
+fn textureGather(component: C, t: texture_2d<T>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('T', ['f32-only', 'i32', 'u32']).
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_cube<T>, s: sampler, coords: vec3<f32>) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('T', ['f32-only', 'i32', 'u32']).
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(3))
+).
+unimplemented();
+
+g.test('sampled_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<T>
+fn textureGather(component: C, t: texture_2d_array<T>, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('T', ['f32-only', 'i32', 'u32']).
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+C: i32, u32
+T: i32, u32, f32
+
+fn textureGather(component: C, t: texture_cube_array<T>, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<T>
+
+Parameters:
+ * component:
+ - The index of the channel to read from the selected texels.
+ - When provided, the component expression must a creation-time expression (e.g. 1).
+ - Its value must be at least 0 and at most 3. Values outside of this range will result in a shader-creation error.
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+`
+).
+paramsSubcasesOnly(
+ (u) =>
+ u.
+ combine('T', ['f32-only', 'i32', 'u32']).
+ combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+ combine('C', ['i32', 'u32']).
+ combine('C_value', [-1, 0, 1, 2, 3, 4]).
+ combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+).
+unimplemented();
+
+g.test('depth_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> vec4<f32>
+fn textureGather(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('depth_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+fn textureGather(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3))
+).
+unimplemented();
+
+g.test('depth_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+C: i32, u32
+
+fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<f32>
+fn textureGather(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('depth_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegather').
+desc(
+ `
+C: i32, u32
+
+fn textureGather(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index
+`
+).
+paramsSubcasesOnly(
+ (u) =>
+ u.
+ combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+ combine('C', ['i32', 'u32']).
+ combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.js
new file mode 100644
index 0000000000..d029c8cdf5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.js
@@ -0,0 +1,134 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureGatherCompare' builtin function
+
+A texture gather compare operation performs a depth comparison on four texels in a depth texture and collects the results into a single vector, as follows:
+ * Find the four texels that would be used in a depth sampling operation with linear filtering, from mip level 0:
+ - Use the specified coordinate, array index (when present), and offset (when present).
+ - The texels are adjacent, forming a square, when considering their texture space coordinates (u,v).
+ - Selected texels at the texture edge, cube face edge, or cube corners are handled as in ordinary texture sampling.
+ * For each texel, perform a comparison against the depth reference value, yielding a 0.0 or 1.0 value, as controlled by the comparison sampler parameters.
+ * Yield the four-component vector where the components are the comparison results with the texels with relative texel coordinates as follows:
+
+ Result component Relative texel coordinate
+ x (umin,vmax)
+ y (umax,vmax)
+ z (umax,vmin)
+ w (umin,vmin)
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegathercompare').
+desc(
+ `
+C: i32, u32
+
+fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> vec4<f32>
+fn textureGatherCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * array_index: The 0-based array index.
+ * depth_ref: The reference value to compare the sampled depth value against
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegathercompare').
+desc(
+ `
+C: i32, u32
+
+fn textureGatherCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * array_index: The 0-based array index.
+ * depth_ref: The reference value to compare the sampled depth value against
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(3)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */])
+).
+unimplemented();
+
+g.test('sampled_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegathercompare').
+desc(
+ `
+fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> vec4<f32>
+fn textureGatherCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * depth_ref: The reference value to compare the sampled depth value against
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturegathercompare').
+desc(
+ `
+fn textureGatherCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> vec4<f32>
+
+Parameters:
+ * t: The depth texture to read from
+ * s: The sampler_comparison
+ * coords: The texture coordinates
+ * depth_ref: The reference value to compare the sampled depth value against
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.js
new file mode 100644
index 0000000000..269f841850
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.js
@@ -0,0 +1,185 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureLoad' builtin function
+
+Reads a single texel from a texture without sampling or filtering.
+
+Returns the unfiltered texel data.
+
+An out of bounds access occurs if:
+ * any element of coords is outside the range [0, textureDimensions(t, level)) for the corresponding element, or
+ * array_index is outside the range [0, textureNumLayers(t)), or
+ * level is outside the range [0, textureNumLevels(t))
+
+If an out of bounds access occurs, the built-in function returns one of:
+ * The data for some texel within bounds of the texture
+ * A vector (0,0,0,0) or (0,0,0,1) of the appropriate type for non-depth textures
+ * 0.0 for depth textures
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_1d').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_1d<T>, coords: C, level: C) -> vec4<T>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+).
+params((u) =>
+u.
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(1)).
+combine('level', [-1, 0, `numlevels-1`, `numlevels`])
+).
+unimplemented();
+
+g.test('sampled_2d').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_2d<T>, coords: vec2<C>, level: C) -> vec4<T>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+).
+params((u) =>
+u.
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(2)).
+combine('level', [-1, 0, `numlevels-1`, `numlevels`])
+).
+unimplemented();
+
+g.test('sampled_3d').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_3d<T>, coords: vec3<C>, level: C) -> vec4<T>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+).
+params((u) =>
+u.
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(3)).
+combine('level', [-1, 0, `numlevels-1`, `numlevels`])
+).
+unimplemented();
+
+g.test('multisampled').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_multisampled_2d<T>, coords: vec2<C>, sample_index: C)-> vec4<T>
+fn textureLoad(t: texture_depth_multisampled_2d, coords: vec2<C>, sample_index: C)-> f32
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * sample_index: The 0-based sample index of the multisampled texture
+`
+).
+params((u) =>
+u.
+combine('texture_type', [
+'texture_multisampled_2d',
+'texture_depth_multisampled_2d']
+).
+beginSubcases().
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(2)).
+combine('sample_index', [-1, 0, `sampleCount-1`, `sampleCount`])
+).
+unimplemented();
+
+g.test('depth').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_depth_2d, coords: vec2<C>, level: C) -> f32
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(2)).
+combine('level', [-1, 0, `numlevels-1`, `numlevels`])
+).
+unimplemented();
+
+g.test('external').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_external, coords: vec2<C>) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+`
+).
+paramsSubcasesOnly((u) =>
+u.combine('C', ['i32', 'u32']).combine('coords', generateCoordBoundaries(2))
+).
+unimplemented();
+
+g.test('arrayed').
+specURL('https://www.w3.org/TR/WGSL/#textureload').
+desc(
+ `
+C is i32 or u32
+
+fn textureLoad(t: texture_2d_array<T>, coords: vec2<C>, array_index: C, level: C) -> vec4<T>
+fn textureLoad(t: texture_depth_2d_array, coords: vec2<C>, array_index: C, level: C) -> f32
+
+Parameters:
+ * t: The sampled texture to read from
+ * coords: The 0-based texel coordinate
+ * array_index: The 0-based texture array index
+ * level: The mip level, with level 0 containing a full size version of the texture
+`
+).
+params((u) =>
+u.
+combine('texture_type', ['texture_2d_array', 'texture_depth_2d_array']).
+beginSubcases().
+combine('C', ['i32', 'u32']).
+combine('coords', generateCoordBoundaries(2)).
+combine('array_index', [-1, 0, `numlayers-1`, `numlayers`]).
+combine('level', [-1, 0, `numlevels-1`, `numlevels`])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.js
new file mode 100644
index 0000000000..30eeed0e88
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLayers.spec.js
@@ -0,0 +1,100 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureNumLayers' builtin function
+
+Returns the number of layers (elements) of an array texture.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled').
+specURL('https://www.w3.org/TR/WGSL/#texturenumlayers').
+desc(
+ `
+T, a sampled type.
+
+fn textureNumLayers(t: texture_2d_array<T>) -> u32
+fn textureNumLayers(t: texture_cube_array<T>) -> u32
+
+Parameters
+ * t The sampled array texture.
+`
+).
+params((u) =>
+u.
+combine('texture_type', ['texture_2d_array', 'texture_cube_array']).
+beginSubcases().
+combine('sampled_type', ['f32-only', 'i32', 'u32'])
+).
+unimplemented();
+
+g.test('arrayed').
+specURL('https://www.w3.org/TR/WGSL/#texturenumlayers').
+desc(
+ `
+fn textureNumLayers(t: texture_depth_2d_array) -> u32
+fn textureNumLayers(t: texture_depth_cube_array) -> u32
+
+Parameters
+ * t The depth array texture.
+`
+).
+params((u) =>
+u.combine('texture_type', ['texture_depth_2d_array', 'texture_depth_cube_array'])
+).
+unimplemented();
+
+g.test('storage').
+specURL('https://www.w3.org/TR/WGSL/#texturenumlayers').
+desc(
+ `
+F: rgba8unorm
+ rgba8snorm
+ rgba8uint
+ rgba8sint
+ rgba16uint
+ rgba16sint
+ rgba16float
+ r32uint
+ r32sint
+ r32float
+ rg32uint
+ rg32sint
+ rg32float
+ rgba32uint
+ rgba32sint
+ rgba32float
+A: read, write, read_write
+
+fn textureNumLayers(t: texture_storage_2d_array<F,A>) -> u32
+
+Parameters
+ * t The sampled storage array texture.
+`
+).
+params((u) =>
+u.
+beginSubcases().
+combine('texel_format', [
+'rgba8unorm',
+'rgba8snorm',
+'rgba8uint',
+'rgba8sint',
+'rgba16uint',
+'rgba16sint',
+'rgba16float',
+'r32uint',
+'r32sint',
+'r32float',
+'rg32uint',
+'rg32sint',
+'rg32float',
+'rgba32uint',
+'rgba32sint',
+'rgba32float']
+).
+combine('access_mode', ['read', 'write', 'read_write'])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.js
new file mode 100644
index 0000000000..69af41b79e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumLevels.spec.js
@@ -0,0 +1,65 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureNumLevels' builtin function
+
+Returns the number of mip levels of a texture.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled').
+specURL('https://www.w3.org/TR/WGSL/#texturenumlevels').
+desc(
+ `
+T, a sampled type.
+
+fn textureNumLevels(t: texture_1d<T>) -> u32
+fn textureNumLevels(t: texture_2d<T>) -> u32
+fn textureNumLevels(t: texture_2d_array<T>) -> u32
+fn textureNumLevels(t: texture_3d<T>) -> u32
+fn textureNumLevels(t: texture_cube<T>) -> u32
+fn textureNumLevels(t: texture_cube_array<T>) -> u32
+
+Parameters
+ * t The sampled array texture.
+`
+).
+params((u) =>
+u.
+combine('texture_type', [
+'texture_1d',
+'texture_2d',
+'texture_2d_array',
+'texture_3d',
+'texture_cube',
+'texture_cube_array`']
+).
+beginSubcases().
+combine('sampled_type', ['f32-only', 'i32', 'u32'])
+).
+unimplemented();
+
+g.test('depth').
+specURL('https://www.w3.org/TR/WGSL/#texturenumlevels').
+desc(
+ `
+fn textureNumLevels(t: texture_depth_2d) -> u32
+fn textureNumLevels(t: texture_depth_2d_array) -> u32
+fn textureNumLevels(t: texture_depth_cube) -> u32
+fn textureNumLevels(t: texture_depth_cube_array) -> u32
+
+Parameters
+ * t The depth array texture.
+`
+).
+params((u) =>
+u.combine('texture_type', [
+'texture_depth_2d',
+'texture_depth_2d_array',
+'texture_depth_cube',
+'texture_depth_cube_array']
+)
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.js
new file mode 100644
index 0000000000..c192bffe04
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureNumSamples.spec.js
@@ -0,0 +1,37 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureNumSamples' builtin function
+
+Returns the number samples per texel in a multisampled texture.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled').
+specURL('https://www.w3.org/TR/WGSL/#texturenumsamples').
+desc(
+ `
+T, a sampled type.
+
+fn textureNumSamples(t: texture_multisampled_2d<T>) -> u32
+
+Parameters
+ * t The multisampled texture.
+`
+).
+params((u) => u.beginSubcases().combine('sampled_type', ['f32-only', 'i32', 'u32'])).
+unimplemented();
+
+g.test('depth').
+specURL('https://www.w3.org/TR/WGSL/#texturenumsamples').
+desc(
+ `
+fn textureNumSamples(t: texture_depth_multisampled_2d) -> u32
+
+Parameters
+ * t The multisampled texture.
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSample.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSample.spec.js
new file mode 100644
index 0000000000..7e4bbd7744
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSample.spec.js
@@ -0,0 +1,273 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Samples a texture.
+
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+Tests that 'textureSample' can only be called in 'fragment' shaders.
+`
+).
+params((u) => u.combine('stage', ['fragment', 'vertex', 'compute'])).
+unimplemented();
+
+g.test('control_flow').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+Tests that 'textureSample' can only be called in uniform control flow.
+`
+).
+params((u) => u.combine('stage', ['fragment', 'vertex', 'compute'])).
+unimplemented();
+
+g.test('sampled_1d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+fn textureSample(t: texture_1d<f32>, s: sampler, coords: f32) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(1))
+).
+unimplemented();
+
+g.test('sampled_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32>
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32>
+fn textureSample(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, offset: vec3<i32>) -> vec4<f32>
+fn textureSample(t: texture_cube<f32>, s: sampler, coords: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+params((u) =>
+u.
+combine('texture_type', ['texture_3d', 'texture_cube']).
+beginSubcases().
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('offset', generateOffsets(3))
+).
+unimplemented();
+
+g.test('depth_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>) -> f32
+fn textureSample(t: texture_depth_2d, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C) -> vec4<f32>
+fn textureSample(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C) -> vec4<f32>
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+`
+).
+paramsSubcasesOnly(
+ (u) =>
+ u.
+ combine('C', ['i32', 'u32']).
+ combine('C_value', [-1, 0, 1, 2, 3, 4]).
+ combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+ combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+).
+unimplemented();
+
+g.test('depth_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+fn textureSample(t: texture_depth_cube, s: sampler, coords: vec3<f32>) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3))
+).
+unimplemented();
+
+g.test('depth_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C) -> f32
+fn textureSample(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('depth_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesample').
+desc(
+ `
+C is i32 or u32
+
+fn textureSample(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C) -> f32
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+`
+).
+paramsSubcasesOnly(
+ (u) =>
+ u.
+ combine('C', ['i32', 'u32']).
+ combine('C_value', [-1, 0, 1, 2, 3, 4]).
+ combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+ combine('coords', generateCoordBoundaries(3))
+ /* array_index not param'd as out-of-bounds is implementation specific */
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.js
new file mode 100644
index 0000000000..7594f3ad5a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.js
@@ -0,0 +1,163 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'textureSampleBias' builtin function
+
+Samples a texture with a bias to the mip level.
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplebias').
+desc(
+ `
+Tests that 'textureSampleBias' can only be called in 'fragment' shaders.
+`
+).
+params((u) => u.combine('stage', ['fragment', 'vertex', 'compute'])).
+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'])).
+unimplemented();
+
+g.test('sampled_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplebias').
+desc(
+ `
+fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32) -> vec4<f32>
+fn textureSampleBias(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, bias: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('bias', [-16.1, -16, 0, 1, 15.99, 16]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplebias').
+desc(
+ `
+fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32>
+fn textureSampleBias(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, bias: f32, offset: vec3<i32>) -> vec4<f32>
+fn textureSampleBias(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, bias: f32) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+params((u) =>
+u.
+combine('texture_type', ['texture_3d', 'texture_cube']).
+beginSubcases().
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('bias', [-16.1, -16, 0, 1, 15.99, 16]).
+combine('offset', generateOffsets(3))
+).
+unimplemented();
+
+g.test('arrayed_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplebias').
+desc(
+ `
+C: i32, u32
+
+fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, bias: f32) -> vec4<f32>
+fn textureSampleBias(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, bias: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index to sample.
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4])
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('bias', [-16.1, -16, 0, 1, 15.99, 16]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('arrayed_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplebias').
+desc(
+ `
+C: i32, u32
+
+fn textureSampleBias(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, bias: f32) -> vec4<f32>
+
+Parameters:
+ * t: The sampled texture to read from
+ * s: The sampler type
+ * coords: The texture coordinates
+ * array_index: The 0-based texture array index to sample.
+ * bias: The bias to apply to the mip level before sampling. bias must be between -16.0 and 15.99.
+ * offset:
+ - The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ This offset is applied before applying any texture wrapping modes.
+ - The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ - Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4])
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('bias', [-16.1, -16, 0, 1, 15.99, 16])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.js
new file mode 100644
index 0000000000..9f90e69f63
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.js
@@ -0,0 +1,145 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Samples a depth texture and compares the sampled depth values against a reference value.
+
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare').
+desc(
+ `
+Tests that 'textureSampleCompare' can only be called in 'fragment' shaders.
+`
+).
+params((u) => u.combine('stage', ['fragment', 'vertex', 'compute'])).
+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'])).
+unimplemented();
+
+g.test('2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare').
+desc(
+ `
+fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
+fn textureSampleCompare(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare').
+desc(
+ `
+fn textureSampleCompare(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */])
+).
+unimplemented();
+
+g.test('arrayed_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> f32
+fn textureSampleCompare(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4])
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('arrayed_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompare(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4])
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.js
new file mode 100644
index 0000000000..f3db871107
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleCompareLevel.spec.js
@@ -0,0 +1,149 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Samples a depth texture and compares the sampled depth values against a reference value.
+
+The textureSampleCompareLevel function is the same as textureSampleCompare, except that:
+
+ * textureSampleCompareLevel always samples texels from mip level 0.
+ * The function does not compute derivatives.
+ * There is no requirement for textureSampleCompareLevel to be invoked in uniform control flow.
+ * textureSampleCompareLevel may be invoked in any shader stage.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel').
+desc(
+ `
+Tests that 'textureSampleCompareLevel' maybe called in any shader stage.
+`
+).
+params((u) => u.combine('stage', ['fragment', 'vertex', 'compute'])).
+unimplemented();
+
+g.test('control_flow').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel').
+desc(
+ `
+Tests that 'textureSampleCompareLevel' maybe called in non-uniform control flow.
+`
+).
+params((u) => u.combine('stage', ['fragment', 'vertex', 'compute'])).
+unimplemented();
+
+g.test('2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel').
+desc(
+ `
+fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32) -> f32
+fn textureSampleCompareLevel(t: texture_depth_2d, s: sampler_comparison, coords: vec2<f32>, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel').
+desc(
+ `
+fn textureSampleCompareLevel(t: texture_depth_cube, s: sampler_comparison, coords: vec3<f32>, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */])
+).
+unimplemented();
+
+g.test('arrayed_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32) -> f32
+fn textureSampleCompareLevel(t: texture_depth_2d_array, s: sampler_comparison, coords: vec2<f32>, array_index: C, depth_ref: f32, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4])
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */]).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('arrayed_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplecomparelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleCompareLevel(t: texture_depth_cube_array, s: sampler_comparison, coords: vec3<f32>, array_index: C, depth_ref: f32) -> f32
+
+Parameters:
+ * t The depth texture to sample.
+ * s The sampler_comparision type.
+ * coords The texture coordinates used for sampling.
+ * array_index: The 0-based texture array index to sample.
+ * depth_ref The reference value to compare the sampled depth value against.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4])
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('depth_ref', [-1 /* smaller ref */, 0 /* equal ref */, 1 /* larger ref */])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.js
new file mode 100644
index 0000000000..dcf121f038
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleGrad.spec.js
@@ -0,0 +1,136 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Samples a texture using explicit gradients.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad').
+desc(
+ `
+fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad').
+desc(
+ `
+fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>, offset: vec3<i32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * coords The texture coordinates used for sampling.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('offset', generateOffsets(3))
+).
+unimplemented();
+
+g.test('sampled_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, ddx: vec2<f32>, ddy: vec2<f32>) -> vec4<f32>
+fn textureSampleGrad(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, ddx: vec2<f32>, ddy: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('offset', generateOffsets(2))
+).
+unimplemented();
+
+g.test('sampled_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplegrad').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleGrad(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, ddx: vec3<f32>, ddy: vec3<f32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled texture.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * ddx The x direction derivative vector used to compute the sampling locations
+ * ddy The y direction derivative vector used to compute the sampling locations
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(3))
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.js
new file mode 100644
index 0000000000..e2cdfff803
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureSampleLevel.spec.js
@@ -0,0 +1,274 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Samples a texture.
+
+Must only be used in a fragment shader stage.
+Must only be invoked in uniform control flow.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+import { generateCoordBoundaries, generateOffsets } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampled_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32) -> vec4<f32>
+fn textureSampleLevel(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, level: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2)).
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented();
+
+g.test('sampled_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, level: f32) -> vec4<f32>
+fn textureSampleLevel(t: texture_2d_array<f32>, s: sampler, coords: vec2<f32>, array_index: C, level: f32, offset: vec2<i32>) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented();
+
+g.test('sampled_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32>
+fn textureSampleLevel(t: texture_3d<f32>, s: sampler, coords: vec3<f32>, level: f32, offset: vec3<i32>) -> vec4<f32>
+fn textureSampleLevel(t: texture_cube<f32>, s: sampler, coords: vec3<f32>, level: f32) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+params((u) =>
+u.
+combine('texture_type', ['texture_3d', 'texture_cube']).
+beginSubcases().
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('coords', generateCoordBoundaries(3)).
+combine('offset', generateOffsets(3)).
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented();
+
+g.test('sampled_array_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_cube_array<f32>, s: sampler, coords: vec3<f32>, array_index: C, level: f32) -> vec4<f32>
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * array_index The 0-based texture array index to sample.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(3)).
+combine('offset', generateOffsets(3))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented();
+
+g.test('depth_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: C) -> f32
+fn textureSampleLevel(t: texture_depth_2d, s: sampler, coords: vec2<f32>, level: C, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2)).
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented();
+
+g.test('depth_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, level: C) -> f32
+fn textureSampleLevel(t: texture_depth_2d_array, s: sampler, coords: vec2<f32>, array_index: C, level: C, offset: vec2<i32>) -> f32
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * array_index The 0-based texture array index to sample.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(2)).
+combine('offset', generateOffsets(2))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented();
+
+g.test('depth_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturesamplelevel').
+desc(
+ `
+C is i32 or u32
+
+fn textureSampleLevel(t: texture_depth_cube, s: sampler, coords: vec3<f32>, level: C) -> f32
+fn textureSampleLevel(t: texture_depth_cube_array, s: sampler, coords: vec3<f32>, array_index: C, level: C) -> f32
+
+Parameters:
+ * t The sampled or depth texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * level
+ * The mip level, with level 0 containing a full size version of the texture.
+ * For the functions where level is a f32, fractional values may interpolate between
+ two levels if the format is filterable according to the Texture Format Capabilities.
+ * When not specified, mip level 0 is sampled.
+ * offset
+ * The optional texel offset applied to the unnormalized texture coordinate before sampling the texture.
+ * This offset is applied before applying any texture wrapping modes.
+ * The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
+ * Each offset component must be at least -8 and at most 7.
+ Values outside of this range will result in a shader-creation error.
+`
+).
+params((u) =>
+u.
+combine('texture_type', ['texture_depth_cube', 'texture_depth_cube_array']).
+beginSubcases().
+combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat']).
+combine('C', ['i32', 'u32']).
+combine('C_value', [-1, 0, 1, 2, 3, 4]).
+combine('coords', generateCoordBoundaries(3))
+/* array_index not param'd as out-of-bounds is implementation specific */.
+combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureStore.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureStore.spec.js
new file mode 100644
index 0000000000..24ee130577
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/textureStore.spec.js
@@ -0,0 +1,122 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Writes a single texel to a texture.
+
+The channel format T depends on the storage texel format F.
+See the texel format table for the mapping of texel format to channel format.
+
+Note: An out-of-bounds access occurs if:
+ * any element of coords is outside the range [0, textureDimensions(t)) for the corresponding element, or
+ * array_index is outside the range of [0, textureNumLayers(t))
+
+If an out-of-bounds access occurs, the built-in function may do any of the following:
+ * not be executed
+ * store value to some in bounds texel
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TexelFormats } from '../../../../types.js';
+
+import { generateCoordBoundaries } from './utils.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('store_1d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturestore').
+desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_1d<F,write>, coords: C, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+).
+params((u) =>
+u.
+combineWithParams(TexelFormats).
+beginSubcases().
+combine('coords', generateCoordBoundaries(1)).
+combine('C', ['i32', 'u32'])
+).
+unimplemented();
+
+g.test('store_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturestore').
+desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_2d<F,write>, coords: vec2<C>, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+).
+params((u) =>
+u.
+combineWithParams(TexelFormats).
+beginSubcases().
+combine('coords', generateCoordBoundaries(2)).
+combine('C', ['i32', 'u32'])
+).
+unimplemented();
+
+g.test('store_array_2d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturestore').
+desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_2d_array<F,write>, coords: vec2<C>, array_index: C, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * array_index The 0-based texture array index
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+).
+params(
+ (u) =>
+ u.
+ combineWithParams(TexelFormats).
+ beginSubcases().
+ combine('coords', generateCoordBoundaries(2)).
+ combine('C', ['i32', 'u32']).
+ combine('C_value', [-1, 0, 1, 2, 3, 4])
+ /* array_index not param'd as out-of-bounds is implementation specific */
+).
+unimplemented();
+
+g.test('store_3d_coords').
+specURL('https://www.w3.org/TR/WGSL/#texturestore').
+desc(
+ `
+C is i32 or u32
+
+fn textureStore(t: texture_storage_3d<F,write>, coords: vec3<C>, value: vec4<T>)
+
+Parameters:
+ * t The sampled, depth, or external texture to sample.
+ * s The sampler type.
+ * coords The texture coordinates used for sampling.
+ * value The new texel value
+`
+).
+params((u) =>
+u.
+combineWithParams(TexelFormats).
+beginSubcases().
+combine('coords', generateCoordBoundaries(3)).
+combine('C', ['i32', 'u32'])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/transpose.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/transpose.spec.js
new file mode 100644
index 0000000000..0e1003b9d6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/transpose.spec.js
@@ -0,0 +1,158 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'transpose' builtin function
+
+T is AbstractFloat, f32, or f16
+@const transpose(e: matRxC<T> ) -> matCxR<T>
+Returns the transpose of e.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ sparseMatrixF16Range,
+ sparseMatrixF32Range,
+ sparseMatrixF64Range } from
+'../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_matCxR_[non_]const
+const f32_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((cols) =>
+[2, 3, 4].map((rows) => ({
+ [`abstract_mat${cols}x${rows}`]: () => {
+ return FP.abstract.generateMatrixToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.transposeInterval
+ );
+ }
+}))
+).
+reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('transpose', {
+ ...f32_cases,
+ ...f16_cases,
+ ...abstract_cases
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`abstract_mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractBuiltin('transpose'),
+ [TypeMat(cols, rows, TypeAbstractFloat)],
+ TypeMat(rows, cols, TypeAbstractFloat),
+ t.params,
+ cases
+ );
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `f32_mat${cols}x${rows}_const` :
+ `f32_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ builtin('transpose'),
+ [TypeMat(cols, rows, TypeF32)],
+ TypeMat(rows, cols, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `f16_mat${cols}x${rows}_const` :
+ `f16_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ builtin('transpose'),
+ [TypeMat(cols, rows, TypeF16)],
+ TypeMat(rows, cols, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/trunc.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/trunc.spec.js
new file mode 100644
index 0000000000..042c02875d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/trunc.spec.js
@@ -0,0 +1,75 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution tests for the 'trunc' builtin function
+
+S is AbstractFloat, f32, f16
+T is S or vecN<S>
+@const fn trunc(e: T ) -> T
+Returns the nearest whole number whose absolute value is less than or equal to e.
+Component-wise when T is a vector.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullF32Range, fullF64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractBuiltin, builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('trunc', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.truncInterval);
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f16.truncInterval);
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range(),
+ 'unfiltered',
+ FP.abstract.truncInterval
+ );
+ }
+});
+
+g.test('abstract_float').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`abstract float tests`).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('abstract');
+ await run(t, abstractBuiltin('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, builtin('trunc'), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions').
+desc(`f16 tests`).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, builtin('trunc'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.js
new file mode 100644
index 0000000000..ca91659889
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Decomposes a 32-bit value into two 16-bit chunks, and reinterpets each chunk as
+a floating point value.
+Component i of the result is the f32 representation of v, where v is the
+interpretation of bits 16×i through 16×i+15 of e as an IEEE-754 binary16 value.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack2x16float', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16floatInterval
+ );
+ }
+});
+
+g.test('unpack').
+specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions').
+desc(
+ `
+@const fn unpack2x16float(e: u32) -> vec2<f32>
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack2x16float'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.js
new file mode 100644
index 0000000000..d8811b5070
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Decomposes a 32-bit value into two 16-bit chunks, then reinterprets each chunk
+as a signed normalized floating point value.
+Component i of the result is max(v ÷ 32767, -1), where v is the interpretation
+of bits 16×i through 16×i+15 of e as a twos-complement signed integer.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack2x16snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16snormInterval
+ );
+ }
+});
+
+g.test('unpack').
+specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions').
+desc(
+ `
+@const fn unpack2x16snorm(e: u32) -> vec2<f32>
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack2x16snorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.js
new file mode 100644
index 0000000000..3e18938080
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Decomposes a 32-bit value into two 16-bit chunks, then reinterprets each chunk
+as an unsigned normalized floating point value.
+Component i of the result is v ÷ 65535, where v is the interpretation of bits
+16×i through 16×i+15 of e as an unsigned integer.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack2x16unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16unormInterval
+ );
+ }
+});
+
+g.test('unpack').
+specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions').
+desc(
+ `
+@const fn unpack2x16unorm(e: u32) -> vec2<f32>
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack2x16unorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.js
new file mode 100644
index 0000000000..3684db6474
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Decomposes a 32-bit value into four 8-bit chunks, then reinterprets each chunk
+as a signed normalized floating point value.
+Component i of the result is max(v ÷ 127, -1), where v is the interpretation of
+bits 8×i through 8×i+7 of e as a twos-complement signed integer.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack4x8snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8snormInterval
+ );
+ }
+});
+
+g.test('unpack').
+specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions').
+desc(
+ `
+@const fn unpack4x8snorm(e: u32) -> vec4<f32>
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack4x8snorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.js
new file mode 100644
index 0000000000..80330885ad
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Decomposes a 32-bit value into four 8-bit chunks, then reinterprets each chunk
+as an unsigned normalized floating point value.
+Component i of the result is v ÷ 255, where v is the interpretation of bits 8×i
+through 8×i+7 of e as an unsigned integer.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { allInputSources, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unpack4x8unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8unormInterval
+ );
+ }
+});
+
+g.test('unpack').
+specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions').
+desc(
+ `
+@const fn unpack4x8unorm(e: u32) -> vec4<f32>
+`
+).
+params((u) => u.combine('inputSource', allInputSources)).
+fn(async (t) => {
+ const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
+ await run(t, builtin('unpack4x8unorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/utils.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/utils.js
new file mode 100644
index 0000000000..448d369d7f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/utils.js
@@ -0,0 +1,45 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Generates the boundary entries for the given number of dimensions
+ *
+ * @param numDimensions: The number of dimensions to generate for
+ * @returns an array of generated coord boundaries
+ */export function generateCoordBoundaries(numDimensions) {const ret = ['in-bounds'];
+
+ if (numDimensions < 1 || numDimensions > 3) {
+ throw new Error(`invalid numDimensions: ${numDimensions}`);
+ }
+
+ const name = 'xyz';
+ for (let i = 0; i < numDimensions; ++i) {
+ for (const j of ['min', 'max']) {
+ for (const k of ['wrap', 'boundary']) {
+ ret.push(`${name[i]}-${j}-${k}`);
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Generates a set of offset values to attempt in the range [-9, 8].
+ *
+ * @param numDimensions: The number of dimensions to generate for
+ * @return an array of generated offset values
+ */
+export function generateOffsets(numDimensions) {
+ if (numDimensions < 2 || numDimensions > 3) {
+ throw new Error(`generateOffsets: invalid numDimensions: ${numDimensions}`);
+ }
+ const ret = [undefined];
+ for (const val of [-9, -8, 0, 1, 7, 8]) {
+ const v = [];
+ for (let i = 0; i < numDimensions; ++i) {
+ v.push(val);
+ }
+ ret.push(v);
+ }
+ return ret;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.js
new file mode 100644
index 0000000000..96848767ff
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/call/builtin/workgroupBarrier.spec.js
@@ -0,0 +1,38 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+'workgroupBarrier' affects memory and atomic operations in the workgroup address space.
+
+All synchronization functions execute a control barrier with Acquire/Release memory ordering.
+That is, all synchronization functions, and affected memory and atomic operations are ordered
+in program order relative to the synchronization function. Additionally, the affected memory
+and atomic operations program-ordered before the synchronization function must be visible to
+all other threads in the workgroup before any affected memory or atomic operation program-ordered
+after the synchronization function is executed by a member of the workgroup. All synchronization
+functions use the Workgroup memory scope. All synchronization functions have a Workgroup
+execution scope.
+
+All synchronization functions must only be used in the compute shader stage.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions').
+desc(
+ `
+All synchronization functions must only be used in the compute shader stage.
+`
+).
+params((u) => u.combine('stage', ['vertex', 'fragment', 'compute'])).
+unimplemented();
+
+g.test('barrier').
+specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions').
+desc(
+ `
+fn workgroupBarrier()
+`
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/case_cache.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/case_cache.js
new file mode 100644
index 0000000000..6b3cb1a4c2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/case_cache.js
@@ -0,0 +1,200 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { dataCache } from '../../../../common/framework/data_cache.js';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 } from
+
+'../../../util/conversion.js';
+import {
+ deserializeFPInterval,
+ FPInterval,
+ serializeFPInterval } from
+'../../../util/floating_point.js';
+import { flatten2DArray, unflatten2DArray } from '../../../util/math.js';
+
+import { isComparator } from './expression.js';var
+
+SerializedExpectationKind = /*#__PURE__*/function (SerializedExpectationKind) {SerializedExpectationKind[SerializedExpectationKind["Value"] = 0] = "Value";SerializedExpectationKind[SerializedExpectationKind["Interval"] = 1] = "Interval";SerializedExpectationKind[SerializedExpectationKind["Interval1DArray"] = 2] = "Interval1DArray";SerializedExpectationKind[SerializedExpectationKind["Interval2DArray"] = 3] = "Interval2DArray";SerializedExpectationKind[SerializedExpectationKind["Array"] = 4] = "Array";SerializedExpectationKind[SerializedExpectationKind["Comparator"] = 5] = "Comparator";return SerializedExpectationKind;}(SerializedExpectationKind || {});
+
+
+
+
+
+
+
+
+/** serializeExpectation() serializes an Expectation to a BinaryStream */
+export function serializeExpectation(s, e) {
+ if (e instanceof Scalar || e instanceof Vector || e instanceof Matrix) {
+ s.writeU8(SerializedExpectationKind.Value);
+ serializeValue(s, e);
+ return;
+ }
+ if (e instanceof FPInterval) {
+ s.writeU8(SerializedExpectationKind.Interval);
+ serializeFPInterval(s, e);
+ return;
+ }
+ if (e instanceof Array) {
+ if (e[0] instanceof Array) {
+ e = e;
+ const cols = e.length;
+ const rows = e[0].length;
+ s.writeU8(SerializedExpectationKind.Interval2DArray);
+ s.writeU16(cols);
+ s.writeU16(rows);
+ s.writeArray(flatten2DArray(e), serializeFPInterval);
+ } else {
+ e = e;
+ s.writeU8(SerializedExpectationKind.Interval1DArray);
+ s.writeArray(e, serializeFPInterval);
+ }
+ return;
+ }
+ if (isComparator(e)) {
+ s.writeU8(SerializedExpectationKind.Comparator);
+ serializeComparator(s, e);
+ return;
+ }
+ unreachable(`cannot serialize Expectation ${e}`);
+}
+
+/** deserializeExpectation() deserializes an Expectation from a BinaryStream */
+export function deserializeExpectation(s) {
+ const kind = s.readU8();
+ switch (kind) {
+ case SerializedExpectationKind.Value:{
+ return deserializeValue(s);
+ }
+ case SerializedExpectationKind.Interval:{
+ return deserializeFPInterval(s);
+ }
+ case SerializedExpectationKind.Interval1DArray:{
+ return s.readArray(deserializeFPInterval);
+ }
+ case SerializedExpectationKind.Interval2DArray:{
+ const cols = s.readU16();
+ const rows = s.readU16();
+ return unflatten2DArray(s.readArray(deserializeFPInterval), cols, rows);
+ }
+ case SerializedExpectationKind.Comparator:{
+ return deserializeComparator(s);
+ }
+ default:{
+ unreachable(`invalid serialized expectation kind: ${kind}`);
+ }
+ }
+}
+
+/** serializeCase() serializes a Case to a BinaryStream */
+export function serializeCase(s, c) {
+ s.writeCond(c.input instanceof Array, {
+ if_true: () => {
+ // c.input is array
+ s.writeArray(c.input, serializeValue);
+ },
+ if_false: () => {
+ // c.input is not array
+ serializeValue(s, c.input);
+ }
+ });
+ serializeExpectation(s, c.expected);
+}
+
+/** deserializeCase() deserializes a Case from a BinaryStream */
+export function deserializeCase(s) {
+ const input = s.readCond({
+ if_true: () => {
+ // c.input is array
+ return s.readArray(deserializeValue);
+ },
+ if_false: () => {
+ // c.input is not array
+ return deserializeValue(s);
+ }
+ });
+ const expected = deserializeExpectation(s);
+ return { input, expected };
+}
+
+/** CaseListBuilder is a function that builds a CaseList */
+
+
+/**
+ * CaseCache is a cache of CaseList.
+ * 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 {
+ /**
+ * 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, builders) {
+ this.path = `webgpu/shader/execution/case-cache/${name}.bin`;
+ this.builders = builders;
+ }
+
+ /** get() returns the list of cases with the given name */
+ async get(name) {
+ const data = await dataCache.fetch(this);
+ return data[name];
+ }
+
+ /**
+ * build() implements the Cacheable.build interface.
+ * @returns the data.
+ */
+ build() {
+ const built = {};
+ for (const name in this.builders) {
+ const cases = this.builders[name]();
+ built[name] = cases;
+ }
+ return Promise.resolve(built);
+ }
+
+ /**
+ * serialize() implements the Cacheable.serialize interface.
+ * @returns the serialized data.
+ */
+ serialize(data) {
+ const maxSize = 32 << 20; // 32MB - max size for a file
+ const stream = new BinaryStream(new ArrayBuffer(maxSize));
+ stream.writeU32(Object.keys(data).length);
+ for (const name in data) {
+ stream.writeString(name);
+ stream.writeArray(data[name], serializeCase);
+ }
+ return stream.buffer();
+ }
+
+ /**
+ * deserialize() implements the Cacheable.deserialize interface.
+ * @returns the deserialize data.
+ */
+ deserialize(array) {
+ const s = new BinaryStream(array.buffer);
+ const casesByName = {};
+ const numRecords = s.readU32();
+ for (let i = 0; i < numRecords; i++) {
+ const name = s.readString();
+ const cases = s.readArray(deserializeCase);
+ casesByName[name] = cases;
+ }
+ return casesByName;
+ }
+
+
+
+}
+
+export function makeCaseCache(name, builders) {
+ return new CaseCache(name, builders);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/expression.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/expression.js
new file mode 100644
index 0000000000..d3d2d96c52
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/expression.js
@@ -0,0 +1,1436 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { globalTestConfig } from '../../../../common/framework/test_config.js';import { assert, objectEquals, unreachable } from '../../../../common/util/util.js';
+
+import { compare } from '../../../util/compare.js';
+import { kValue } from '../../../util/constants.js';
+import {
+ ScalarType,
+ Scalar,
+
+ TypeVec,
+ TypeU32,
+
+ Vector,
+ VectorType,
+ u32,
+ i32,
+ Matrix,
+ MatrixType,
+
+ scalarTypeOf } from
+'../../../util/conversion.js';
+import { FPInterval } from '../../../util/floating_point.js';
+import {
+ cartesianProduct,
+
+ quantizeToI32,
+ quantizeToU32 } from
+'../../../util/math.js';
+
+
+
+
+
+
+
+
+/** @returns if this Expectation actually a Comparator */
+export function isComparator(e) {
+ 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) {
+ if (isComparator(input)) {
+ return input;
+ }
+
+ return { compare: (got) => compare(got, input), kind: 'value' };
+}
+
+/** Case is a single expression test case. */
+
+
+
+
+
+
+
+/** CaseList is a list of Cases */
+
+
+/** The input value source */
+
+
+
+
+// Read-write storage buffer
+
+/** All possible input sources */
+export const allInputSources = ['const', 'uniform', 'storage_r', 'storage_rw'];
+
+/** Just constant input source */
+export const onlyConstInputSource = ['const'];
+
+/** Configuration for running a expression test */
+
+
+
+
+
+
+
+
+
+
+
+
+// Helper for returning the stride for a given Type
+function valueStride(ty) {
+ // 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;
+ }
+ }
+ unreachable(`AbstractFloats have not yet been implemented for ${ty.toString()}`);
+ }
+
+ 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;
+ }
+ unreachable(
+ `Attempted to get stride length for a matrix with dimensions (${ty.cols}x${ty.rows}), which isn't currently handled`
+ );
+ }
+
+ // Handles scalars and vectors
+ return 16;
+}
+
+// Helper for summing up all of the stride values for an array of Types
+function valueStrides(tys) {
+ return tys.map(valueStride).reduce((sum, c) => sum + c);
+}
+
+// Helper for returning the WGSL storage type for the given Type.
+function storageType(ty) {
+ if (ty instanceof ScalarType) {
+ assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`);
+ assert(
+ ty.kind !== 'abstract-float',
+ `Custom handling is implemented for 'abstract-float' values`
+ );
+ if (ty.kind === 'bool') {
+ return TypeU32;
+ }
+ }
+ if (ty instanceof VectorType) {
+ return TypeVec(ty.width, storageType(ty.elementType));
+ }
+ return ty;
+}
+
+// Helper for converting a value of the type 'ty' from the storage type.
+function fromStorage(ty, expr) {
+ if (ty instanceof ScalarType) {
+ assert(ty.kind !== 'abstract-float', `AbstractFloat 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`;
+ }
+ }
+ if (ty instanceof VectorType) {
+ assert(
+ ty.elementType.kind !== 'abstract-float',
+ `AbstractFloat 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;
+}
+
+// Helper for converting a value of the type 'ty' to the storage type.
+function toStorage(ty, expr) {
+ if (ty instanceof ScalarType) {
+ assert(
+ ty.kind !== 'abstract-float',
+ `AbstractFloat values have custom code for writing to storage`
+ );
+ assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`);
+ if (ty.kind === 'bool') {
+ return `select(0u, 1u, ${expr})`;
+ }
+ }
+ if (ty instanceof VectorType) {
+ assert(
+ ty.elementType.kind !== 'abstract-float',
+ `AbstractFloat values have custom code for writing to storage`
+ );
+ assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`);
+ if (ty.elementType.kind === 'bool') {
+ return `select(vec${ty.width}<u32>(0u), vec${ty.width}<u32>(1u), ${expr})`;
+ }
+ }
+ return expr;
+}
+
+// A Pipeline is a map of WGSL shader source to a built pipeline
+
+
+/**
+ * Searches for an entry with the given key, adding and returning the result of calling
+ * `create` if the entry was not found.
+ * @param map the cache map
+ * @param key the entry's key
+ * @param create the function used to construct a value, if not found in the cache
+ * @returns the value, either fetched from the cache, or newly built.
+ */
+function getOrCreate(map, key, create) {
+ const existing = map.get(key);
+ if (existing !== undefined) {
+ return existing;
+ }
+ const value = create();
+ map.set(key, value);
+ return value;
+}
+
+/**
+ * Runs the list of expression tests, possibly splitting the tests into multiple
+ * dispatches to keep the input data within the buffer binding limits.
+ * run() will pack the scalar test cases into smaller set of vectorized tests
+ * if `cfg.vectorize` is defined.
+ * @param t the GPUTest
+ * @param shaderBuilder the shader builder function
+ * @param parameterTypes the list of expression parameter types
+ * @param resultType the return type for the expression overload
+ * @param cfg test configuration values
+ * @param cases list of test cases
+ * @param batch_size override the calculated casesPerBatch.
+ */
+export async function run(
+t,
+shaderBuilder,
+parameterTypes,
+resultType,
+cfg = { inputSource: 'storage_r' },
+cases,
+batch_size)
+{
+ // If the 'vectorize' config option was provided, pack the cases into vectors.
+ if (cfg.vectorize !== undefined) {
+ const packed = packScalarsToVector(parameterTypes, resultType, cases, cfg.vectorize);
+ cases = packed.cases;
+ parameterTypes = packed.parameterTypes;
+ resultType = packed.resultType;
+ }
+
+ // The size of the input buffer may exceed the maximum buffer binding size,
+ // so chunk the tests up into batches that fit into the limits. We also split
+ // the cases into smaller batches to help with shader compilation performance.
+ const casesPerBatch = function () {
+ if (batch_size) {
+ return batch_size;
+ }
+ switch (cfg.inputSource) {
+ case 'const':
+ // Some drivers are slow to optimize shaders with many constant values,
+ // or statements. 32 is an empirically picked number of cases that works
+ // well for most drivers.
+ return 32;
+ case 'uniform':
+ // Some drivers are slow to build pipelines with large uniform buffers.
+ // 2k appears to be a sweet-spot when benchmarking.
+ return Math.floor(
+ Math.min(1024 * 2, t.device.limits.maxUniformBufferBindingSize) /
+ valueStrides(parameterTypes)
+ );
+ case 'storage_r':
+ case 'storage_rw':
+ return Math.floor(
+ t.device.limits.maxStorageBufferBindingSize / valueStrides(parameterTypes)
+ );
+ }
+ }();
+
+ // A cache to hold built shader pipelines.
+ const pipelineCache = new Map();
+
+ // Submit all the cases in batches, rate-limiting to ensure not too many
+ // batches are in flight simultaneously.
+ const maxBatchesInFlight = 5;
+ let batchesInFlight = 0;
+ let resolvePromiseBlockingBatch = undefined;
+ const batchFinishedCallback = () => {
+ batchesInFlight -= 1;
+ // If there is any batch waiting on a previous batch to finish,
+ // unblock it now, and clear the resolve callback.
+ if (resolvePromiseBlockingBatch) {
+ resolvePromiseBlockingBatch();
+ resolvePromiseBlockingBatch = undefined;
+ }
+ };
+
+ const processBatch = async (batchCases) => {
+ const checkBatch = await submitBatch(
+ t,
+ shaderBuilder,
+ parameterTypes,
+ resultType,
+ batchCases,
+ cfg.inputSource,
+ pipelineCache
+ );
+ checkBatch();
+ void t.queue.onSubmittedWorkDone().finally(batchFinishedCallback);
+ };
+
+ const pendingBatches = [];
+
+ for (let i = 0; i < cases.length; i += casesPerBatch) {
+ const batchCases = cases.slice(i, Math.min(i + casesPerBatch, cases.length));
+
+ if (batchesInFlight > maxBatchesInFlight) {
+ await new Promise((resolve) => {
+ // There should only be one batch waiting at a time.
+ assert(resolvePromiseBlockingBatch === undefined);
+ resolvePromiseBlockingBatch = resolve;
+ });
+ }
+ batchesInFlight += 1;
+
+ pendingBatches.push(processBatch(batchCases));
+ }
+
+ await Promise.all(pendingBatches);
+}
+
+/**
+ * Submits the list of expression tests. The input data must fit within the
+ * buffer binding limits of the given inputSource.
+ * @param t the GPUTest
+ * @param shaderBuilder the shader builder function
+ * @param parameterTypes the list of expression parameter types
+ * @param resultType the return type for the expression overload
+ * @param cases list of test cases that fit within the binding limits of the device
+ * @param inputSource the source of the input values
+ * @param pipelineCache the cache of compute pipelines, shared between batches
+ * @returns a function that checks the results are as expected
+ */
+async function submitBatch(
+t,
+shaderBuilder,
+parameterTypes,
+resultType,
+cases,
+inputSource,
+pipelineCache)
+{
+ // Construct a buffer to hold the results of the expression tests
+ const outputBufferSize = cases.length * valueStride(resultType);
+ const outputBuffer = t.device.createBuffer({
+ size: outputBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ });
+
+ const [pipeline, group] = await buildPipeline(
+ t,
+ shaderBuilder,
+ parameterTypes,
+ resultType,
+ cases,
+ inputSource,
+ outputBuffer,
+ pipelineCache
+ );
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, group);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+
+ // Heartbeat to ensure CTS runners know we're alive.
+ globalTestConfig.testHeartbeatCallback();
+
+ t.queue.submit([encoder.finish()]);
+
+ // Return a function that can check the results of the shader
+ return () => {
+ const checkExpectation = (outputData) => {
+ // Read the outputs from the output buffer
+ const outputs = new Array(cases.length);
+ for (let i = 0; i < cases.length; i++) {
+ outputs[i] = resultType.read(outputData, i * valueStride(resultType));
+ }
+
+ // The list of expectation failures
+ const errs = [];
+
+ // For each case...
+ for (let caseIdx = 0; caseIdx < cases.length; caseIdx++) {
+ const c = cases[caseIdx];
+ const got = outputs[caseIdx];
+ const cmp = toComparator(c.expected).compare(got);
+ if (!cmp.matched) {
+ errs.push(`(${c.input instanceof Array ? c.input.join(', ') : c.input})
+ returned: ${cmp.got}
+ expected: ${cmp.expected}`);
+ }
+ }
+
+ return errs.length > 0 ? new Error(errs.join('\n\n')) : undefined;
+ };
+
+ // Heartbeat to ensure CTS runners know we're alive.
+ globalTestConfig.testHeartbeatCallback();
+
+ t.expectGPUBufferValuesPassCheck(outputBuffer, checkExpectation, {
+ type: Uint8Array,
+ typedLength: outputBufferSize
+ });
+ };
+}
+
+/**
+ * map is a helper for returning a new array with each element of `v`
+ * transformed with `fn`.
+ * If `v` is not an array, then `fn` is called with (v, 0).
+ */
+function map(v, fn) {
+ if (v instanceof Array) {
+ return v.map(fn);
+ }
+ return [fn(v, 0)];
+}
+
+/**
+ * ShaderBuilder is a function used to construct the WGSL shader used by an
+ * expression test.
+ * @param parameterTypes the list of expression parameter types
+ * @param resultType the return type for the expression overload
+ * @param cases list of test cases that fit within the binding limits of the device
+ * @param inputSource the source of the input values
+ */
+
+
+
+
+
+
+
+/**
+ * Helper that returns the WGSL to declare the output storage buffer for a shader
+ */
+function wgslOutputs(resultType, count) {
+ let output_struct = undefined;
+ if (scalarTypeOf(resultType).kind !== 'abstract-float') {
+ output_struct = `
+struct Output {
+ @size(${valueStride(resultType)}) value : ${storageType(resultType)}
+};`;
+ } else {
+ if (resultType instanceof ScalarType) {
+ output_struct = `struct AF {
+ low: u32,
+ high: u32,
+};
+
+struct Output {
+ @size(${valueStride(resultType)}) value: AF,
+};`;
+ }
+ if (resultType instanceof VectorType) {
+ const dim = resultType.width;
+ output_struct = `struct AF {
+ low: u32,
+ high: u32,
+};
+
+struct Output {
+ @size(${valueStride(resultType)}) value: array<AF, ${dim}>,
+};`;
+ }
+
+ if (resultType instanceof MatrixType) {
+ const cols = resultType.cols;
+ const rows = resultType.rows === 2 ? 2 : 4; // 3 element rows have a padding element
+ output_struct = `struct AF {
+ low: u32,
+ high: u32,
+};
+
+struct Output {
+ @size(${valueStride(resultType)}) value: array<array<AF, ${rows}>, ${cols}>,
+};`;
+ }
+
+ assert(output_struct !== undefined, `No implementation for result type '${resultType}'`);
+ }
+
+ return `${output_struct}
+@group(0) @binding(0) var<storage, read_write> outputs : array<Output, ${count}>;
+`;
+}
+
+/**
+ * Helper that returns the WGSL to declare the values array for a shader
+ */
+function wgslValuesArray(
+parameterTypes,
+resultType,
+cases,
+expressionBuilder)
+{
+ return `
+const values = array(
+ ${cases.map((c) => expressionBuilder(map(c.input, (v) => v.wgsl()))).join(',\n ')}
+);`;
+}
+
+/**
+ * Helper that returns the WGSL 'var' declaration for the given input source
+ */
+function wgslInputVar(inputSource, count) {
+ switch (inputSource) {
+ case 'storage_r':
+ return `@group(0) @binding(1) var<storage, read> inputs : array<Input, ${count}>;`;
+ case 'storage_rw':
+ return `@group(0) @binding(1) var<storage, read_write> inputs : array<Input, ${count}>;`;
+ case 'uniform':
+ return `@group(0) @binding(1) var<uniform> inputs : array<Input, ${count}>;`;
+ }
+ throw new Error(`InputSource ${inputSource} does not use an input var`);
+}
+
+/**
+ * Helper that returns the WGSL header before any other declaration, currently include f16
+ * enable directive if necessary.
+ */
+function wgslHeader(parameterTypes, resultType) {
+ const usedF16 =
+ scalarTypeOf(resultType).kind === 'f16' ||
+ parameterTypes.some((ty) => scalarTypeOf(ty).kind === 'f16');
+ const header = usedF16 ? 'enable f16;\n' : '';
+ return header;
+}
+
+/**
+ * ExpressionBuilder returns the WGSL used to evaluate an expression with the
+ * given input values.
+ */
+
+
+/**
+ * Returns a ShaderBuilder that builds a basic expression test shader.
+ * @param expressionBuilder the expression builder
+ */
+function basicExpressionShaderBody(
+expressionBuilder,
+parameterTypes,
+resultType,
+cases,
+inputSource)
+{
+ assert(
+ scalarTypeOf(resultType).kind !== 'abstract-float',
+ `abstractFloatShaderBuilder should be used when result type is 'abstract-float`
+ );
+ if (inputSource === 'const') {
+ //////////////////////////////////////////////////////////////////////////
+ // Constant eval
+ //////////////////////////////////////////////////////////////////////////
+ let body = '';
+ if (parameterTypes.some((ty) => scalarTypeOf(ty).kind === 'abstract-float')) {
+ // Directly assign the expression to the output, to avoid an
+ // intermediate store, which will concretize the value early
+ body = cases.
+ map(
+ (c, i) =>
+ ` outputs[${i}].value = ${toStorage(
+ resultType,
+ expressionBuilder(map(c.input, (v) => v.wgsl()))
+ )};`
+ ).
+ join('\n ');
+ } else if (globalTestConfig.unrollConstEvalLoops) {
+ body = cases.
+ map((_, i) => {
+ const value = `values[${i}]`;
+ return ` outputs[${i}].value = ${toStorage(resultType, value)};`;
+ }).
+ join('\n ');
+ } else {
+ body = `
+ for (var i = 0u; i < ${cases.length}; i++) {
+ outputs[i].value = ${toStorage(resultType, `values[i]`)};
+ }`;
+ }
+
+ return `
+${wgslOutputs(resultType, cases.length)}
+
+${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)}
+
+@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, i) => fromStorage(ty, `inputs[i].param${i}`);
+
+ // resolves to the expression that calls the builtin
+ const expr = toStorage(resultType, expressionBuilder(parameterTypes.map(paramExpr)));
+
+ return `
+struct Input {
+${parameterTypes.
+ map((ty, i) => ` @size(${valueStride(ty)}) param${i} : ${storageType(ty)},`).
+ join('\n')}
+};
+
+${wgslOutputs(resultType, cases.length)}
+
+${wgslInputVar(inputSource, cases.length)}
+
+@compute @workgroup_size(1)
+fn main() {
+ for (var i = 0; i < ${cases.length}; i++) {
+ outputs[i].value = ${expr};
+ }
+}
+`;
+ }
+}
+
+/**
+ * Returns a ShaderBuilder that builds a basic expression test shader.
+ * @param expressionBuilder the expression builder
+ */
+export function basicExpressionBuilder(expressionBuilder) {
+ return (
+ parameterTypes,
+ resultType,
+ cases,
+ inputSource) =>
+ {
+ return `\
+${wgslHeader(parameterTypes, resultType)}
+
+${basicExpressionShaderBody(expressionBuilder, parameterTypes, resultType, cases, inputSource)}`;
+ };
+}
+
+/**
+ * Returns a ShaderBuilder that builds a basic expression test shader with given predeclaration
+ * string goes after WGSL header (i.e. enable directives) if any but before anything else.
+ * @param expressionBuilder the expression builder
+ * @param predeclaration the predeclaration string
+ */
+export function basicExpressionWithPredeclarationBuilder(
+expressionBuilder,
+predeclaration)
+{
+ return (
+ parameterTypes,
+ resultType,
+ cases,
+ inputSource) =>
+ {
+ return `\
+${wgslHeader(parameterTypes, resultType)}
+
+${predeclaration}
+
+${basicExpressionShaderBody(expressionBuilder, parameterTypes, resultType, cases, inputSource)}`;
+ };
+}
+
+/**
+ * Returns a ShaderBuilder that builds a compound assignment operator test shader.
+ * @param op the compound operator
+ */
+export function compoundAssignmentBuilder(op) {
+ return (
+ parameterTypes,
+ resultType,
+ cases,
+ inputSource) =>
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // Input validation
+ //////////////////////////////////////////////////////////////////////////
+ if (parameterTypes.length !== 2) {
+ throw new Error(`compoundBinaryOp() requires exactly two parameters values per case`);
+ }
+ const lhsType = parameterTypes[0];
+ const rhsType = parameterTypes[1];
+ if (!objectEquals(lhsType, resultType)) {
+ throw new Error(
+ `compoundBinaryOp() requires result type (${resultType}) to be equal to the LHS type (${lhsType})`
+ );
+ }
+ if (inputSource === 'const') {
+ //////////////////////////////////////////////////////////////////////////
+ // Constant eval
+ //////////////////////////////////////////////////////////////////////////
+ let body = '';
+ if (globalTestConfig.unrollConstEvalLoops) {
+ body = cases.
+ map((_, i) => {
+ return `
+ var ret_${i} = lhs[${i}];
+ ret_${i} ${op} rhs[${i}];
+ outputs[${i}].value = ${storageType(resultType)}(ret_${i});`;
+ }).
+ join('\n ');
+ } else {
+ body = `
+ for (var i = 0u; i < ${cases.length}; i++) {
+ var ret = lhs[i];
+ ret ${op} rhs[i];
+ outputs[i].value = ${storageType(resultType)}(ret);
+ }`;
+ }
+
+ const values = cases.map((c) => c.input.map((v) => v.wgsl()));
+
+ return `
+${wgslHeader(parameterTypes, resultType)}
+${wgslOutputs(resultType, cases.length)}
+
+const lhs = array(
+${values.map((c) => `${c[0]}`).join(',\n ')}
+ );
+const rhs = array(
+${values.map((c) => `${c[1]}`).join(',\n ')}
+);
+
+@compute @workgroup_size(1)
+fn main() {
+${body}
+}`;
+ } else {
+ //////////////////////////////////////////////////////////////////////////
+ // Runtime eval
+ //////////////////////////////////////////////////////////////////////////
+ return `
+${wgslHeader(parameterTypes, resultType)}
+${wgslOutputs(resultType, cases.length)}
+
+struct Input {
+ @size(${valueStride(lhsType)}) lhs : ${storageType(lhsType)},
+ @size(${valueStride(rhsType)}) rhs : ${storageType(rhsType)},
+}
+
+${wgslInputVar(inputSource, cases.length)}
+
+@compute @workgroup_size(1)
+fn main() {
+ for (var i = 0; i < ${cases.length}; i++) {
+ var ret = ${lhsType}(inputs[i].lhs);
+ ret ${op} ${rhsType}(inputs[i].rhs);
+ outputs[i].value = ${storageType(resultType)}(ret);
+ }
+}
+`;
+ }
+ };
+}
+
+/**
+ * @returns a string that extracts the value of an AbstractFloat into an output
+ * destination
+ * @param expr expression for an AbstractFloat value, if working with vectors or
+ * matrices, this string needs to include indexing into the
+ * container.
+ * @param case_idx index in the case output array to assign the result
+ * @param accessor string representing how access to the AF that needs to be
+ * operated on.
+ * For scalars this should be left as ''.
+ * For vectors this will be an indexing operation,
+ * i.e. '[i]'
+ * For matrices this will double indexing operation,
+ * i.e. '[c][r]'
+ */
+function abstractFloatSnippet(expr, case_idx, accessor = '') {
+ // AbstractFloats are f64s under the hood. WebGPU does not support
+ // putting f64s in buffers, so the result needs to be split up into u32s
+ // and rebuilt in the test framework.
+ //
+ // Since there is no 64-bit data type that can be used as an element for a
+ // vector or a matrix in WGSL, the testing framework needs to pass the u32s
+ // via a struct with two u32s, and deconstruct vectors and matrices into
+ // arrays.
+ //
+ // This is complicated by the fact that user defined functions cannot
+ // take/return AbstractFloats, and AbstractFloats cannot be stored in
+ // variables, so the code cannot just inject a simple utility function
+ // at the top of the shader, instead this snippet needs to be inlined
+ // everywhere the test needs to return an AbstractFloat.
+ //
+ // select is used below, since ifs are not available during constant
+ // eval. This has the side effect of short-circuiting doesn't occur, so
+ // both sides of the select have to evaluate and be valid.
+ //
+ // This snippet implements FTZ for subnormals to bypass the need for
+ // complex subnormal specific logic.
+ //
+ // Expressions resulting in subnormals can still be reasonably tested,
+ // since this snippet will return 0 with the correct sign, which is
+ // always in the acceptance interval for a subnormal result, since an
+ // implementation may FTZ.
+ //
+ // Documentation for the snippet working with scalar results is included here
+ // in this code block, since shader length affects compilation time
+ // significantly on some backends. The code for vectors and matrices basically
+ // the same thing, with extra indexing operations.
+ //
+ // Snippet with documentation:
+ // const kExponentBias = 1022;
+ //
+ // // Detect if the value is zero or subnormal, so that FTZ behaviour
+ // // can occur
+ // const subnormal_or_zero : bool = (${expr} <= ${kValue.f64.positive.subnormal.max}) && (${expr} >= ${kValue.f64.negative.subnormal.min});
+ //
+ // // MSB of the upper u32 is 1 if the value is negative, otherwise 0
+ // // Extract the sign bit early, so that abs() can be used with
+ // // frexp() so negative cases do not need to be handled
+ // const sign_bit : u32 = select(0, 0x80000000, ${expr} < 0);
+ //
+ // // Use frexp() to obtain the exponent and fractional parts, and
+ // // then perform FTZ if needed
+ // const f = frexp(abs(${expr}));
+ // const f_fract = select(f.fract, 0, subnormal_or_zero);
+ // const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero);
+ //
+ // // Adjust for the exponent bias and shift for storing in bits
+ // // [20..31] of the upper u32
+ // const exponent_bits : u32 = (f_exp + kExponentBias) << 20;
+ //
+ // // Extract the portion of the mantissa that appears in upper u32 as
+ // // a float for later use
+ // const high_mantissa = ldexp(f_fract, 21);
+ //
+ // // Extract the portion of the mantissa that appears in upper u32 as
+ // // as bits. This value is masked, because normals will explicitly
+ // // have the implicit leading 1 that should not be in the final
+ // // result.
+ // const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff;
+ //
+ // // Calculate the mantissa stored in the lower u32 as a float
+ // const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21);
+ //
+ // // Convert the lower u32 mantissa to bits
+ // const low_mantissa_bits = u32(ldexp(low_mantissa, 53));
+ //
+ // outputs[${i}].value.high = sign_bit | exponent_bits | high_mantissa_bits;
+ // outputs[${i}].value.low = low_mantissa_bits;
+
+ return ` {
+ const kExponentBias = 1022;
+ const subnormal_or_zero : bool = (${expr}${accessor} <= ${kValue.f64.positive.subnormal.max}) && (${expr}${accessor} >= ${kValue.f64.negative.subnormal.min});
+ const sign_bit : u32 = select(0, 0x80000000, ${expr}${accessor} < 0);
+ const f = frexp(abs(${expr}${accessor}));
+ const f_fract = select(f.fract, 0, subnormal_or_zero);
+ const f_exp = select(f.exp, -kExponentBias, subnormal_or_zero);
+ const exponent_bits : u32 = (f_exp + kExponentBias) << 20;
+ const high_mantissa = ldexp(f_fract, 21);
+ const high_mantissa_bits : u32 = u32(ldexp(f_fract, 21)) & 0x000fffff;
+ const low_mantissa = f_fract - ldexp(floor(high_mantissa), -21);
+ const low_mantissa_bits = u32(ldexp(low_mantissa, 53));
+ outputs[${case_idx}].value${accessor}.high = sign_bit | exponent_bits | high_mantissa_bits;
+ outputs[${case_idx}].value${accessor}.low = low_mantissa_bits;
+ }`;
+}
+
+/** @returns a string for a specific case that has a AbstractFloat result */
+function abstractFloatCaseBody(expr, resultType, i) {
+ if (resultType instanceof ScalarType) {
+ return abstractFloatSnippet(expr, i);
+ }
+
+ if (resultType instanceof VectorType) {
+ return [...Array(resultType.width).keys()].
+ map((idx) => abstractFloatSnippet(expr, i, `[${idx}]`)).
+ join(' \n');
+ }
+
+ if (resultType instanceof MatrixType) {
+ const cols = resultType.cols;
+ const rows = resultType.rows;
+ const results = [...Array(cols * rows)];
+
+ for (let c = 0; c < cols; c++) {
+ for (let r = 0; r < rows; r++) {
+ results[c * rows + r] = abstractFloatSnippet(expr, i, `[${c}][${r}]`);
+ }
+ }
+
+ return results.join(' \n');
+ }
+
+ unreachable(`Results of type '${resultType}' not yet implemented`);
+}
+
+/**
+ * @returns a ShaderBuilder that builds a test shader hands AbstractFloat results.
+ * @param expressionBuilder an expression builder that will return AbstractFloats
+ */
+export function abstractFloatShaderBuilder(expressionBuilder) {
+ return (
+ parameterTypes,
+ resultType,
+ cases,
+ inputSource) =>
+ {
+ assert(inputSource === 'const', 'AbstractFloat results are only defined for const-eval');
+ assert(
+ scalarTypeOf(resultType).kind === 'abstract-float',
+ `Expected resultType of 'abstract-float', received '${scalarTypeOf(resultType).kind}' instead`
+ );
+
+ const body = cases.
+ map((c, i) => {
+ const expr = `${expressionBuilder(map(c.input, (v) => v.wgsl()))}`;
+ return abstractFloatCaseBody(expr, resultType, i);
+ }).
+ join('\n ');
+
+ return `
+${wgslHeader(parameterTypes, resultType)}
+
+${wgslOutputs(resultType, cases.length)}
+
+@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
+ * pipeline.
+ * @param t the GPUTest
+ * @param shaderBuilder the shader builder
+ * @param parameterTypes the list of expression parameter types
+ * @param resultType the return type for the expression overload
+ * @param cases list of test cases that fit within the binding limits of the device
+ * @param inputSource the source of the input values
+ * @param outputBuffer the buffer that will hold the output values of the tests
+ * @param pipelineCache the cache of compute pipelines, shared between batches
+ */
+async function buildPipeline(
+t,
+shaderBuilder,
+parameterTypes,
+resultType,
+cases,
+inputSource,
+outputBuffer,
+pipelineCache)
+{
+ cases.forEach((c) => {
+ const inputTypes = c.input instanceof Array ? c.input.map((i) => i.type) : [c.input.type];
+ if (!objectEquals(inputTypes, parameterTypes)) {
+ const input_str = `[${inputTypes.join(',')}]`;
+ const param_str = `[${parameterTypes.join(',')}]`;
+ throw new Error(
+ `case input types ${input_str} do not match provided runner parameter types ${param_str}`
+ );
+ }
+ });
+
+ const source = shaderBuilder(parameterTypes, resultType, cases, inputSource);
+
+ switch (inputSource) {
+ case 'const':{
+ // build the shader module
+ const module = t.device.createShaderModule({ code: source });
+
+ // 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: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ return [pipeline, group];
+ }
+
+ case 'uniform':
+ case 'storage_r':
+ case 'storage_rw':{
+ // Input values come from a uniform or storage buffer
+
+ // size in bytes of the input buffer
+ const inputSize = cases.length * valueStrides(parameterTypes);
+
+ // 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]);
+ }
+ }
+ }
+
+ // build the compute pipeline, if the shader hasn't been compiled already.
+ const pipeline = getOrCreate(pipelineCache, source, () => {
+ // build the shader module
+ const module = t.device.createShaderModule({ code: source });
+
+ // build the pipeline
+ return t.device.createComputePipeline({
+ layout: 'auto',
+ compute: { module, entryPoint: 'main' }
+ });
+ });
+
+ // build the input buffer
+ const inputBuffer = t.makeBufferWithContents(
+ inputData,
+ GPUBufferUsage.COPY_SRC | (
+ inputSource === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE)
+ );
+
+ // build the bind group
+ const group = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: outputBuffer } },
+ { binding: 1, resource: { buffer: inputBuffer } }]
+
+ });
+
+ return [pipeline, group];
+ }
+ }
+}
+
+/**
+ * Packs a list of scalar test cases into a smaller list of vector cases.
+ * Requires that all parameters of the expression overload are of a scalar type,
+ * and the return type of the expression overload is also a scalar type.
+ * 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(
+parameterTypes,
+resultType,
+cases,
+vectorWidth)
+{
+ // Validate that the parameters and return type are all vectorizable
+ for (let i = 0; i < parameterTypes.length; i++) {
+ const ty = parameterTypes[i];
+ if (!(ty instanceof ScalarType)) {
+ throw new Error(
+ `packScalarsToVector() can only be used on scalar parameter types, but the ${i}'th parameter type is a ${ty}'`
+ );
+ }
+ }
+ if (!(resultType instanceof ScalarType)) {
+ throw new Error(
+ `packScalarsToVector() can only be used with a scalar return type, but the return type is a ${resultType}'`
+ );
+ }
+
+ const packedCases = [];
+ const packedParameterTypes = parameterTypes.map((p) => TypeVec(vectorWidth, p));
+ const packedResultType = new VectorType(vectorWidth, resultType);
+
+ const clampCaseIdx = (idx) => 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(parameterTypes.length);
+ for (let paramIdx = 0; paramIdx < parameterTypes.length; paramIdx++) {
+ const inputElements = new Array(vectorWidth);
+ for (let i = 0; i < vectorWidth; i++) {
+ const input = cases[clampCaseIdx(caseIdx + i)].input;
+ inputElements[i] = input instanceof Array ? input[paramIdx] : input;
+ }
+ packedInputs[paramIdx] = new Vector(inputElements);
+ }
+
+ // Gather the comparators for the packed cases
+ const cmp_impls = new Array(vectorWidth);
+ for (let i = 0; i < vectorWidth; i++) {
+ cmp_impls[i] = toComparator(cases[clampCaseIdx(caseIdx + i)].expected).compare;
+ }
+ const comparators = {
+ compare: (got) => {
+ let matched = true;
+ const gElements = new Array(vectorWidth);
+ const eElements = new Array(vectorWidth);
+ for (let i = 0; i < vectorWidth; i++) {
+ const d = cmp_impls[i](got.elements[i]);
+ matched = matched && d.matched;
+ gElements[i] = d.got;
+ eElements[i] = d.expected;
+ }
+ return {
+ matched,
+ got: `${packedResultType}(${gElements.join(', ')})`,
+ expected: `${packedResultType}(${eElements.join(', ')})`
+ };
+ },
+ kind: 'packed'
+ };
+
+ // Append the new packed case
+ packedCases.push({ input: packedInputs, expected: comparators });
+ caseIdx += vectorWidth;
+ }
+
+ return {
+ cases: packedCases,
+ parameterTypes: packedParameterTypes,
+ 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.
+ */
+
+
+// No expectations
+
+/**
+ * A function that performs a binary operation on x and y, and returns the expected
+ * result.
+ */
+
+
+
+
+/**
+ * @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,
+param1s,
+op,
+quantize,
+scalarize)
+{
+ 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());
+}
+
+/**
+ * @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,
+param1s,
+op)
+{
+ 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,
+param1s,
+op)
+{
+ 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,
+vector,
+op,
+quantize,
+scalarize)
+{
+ 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.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,
+vectors,
+op,
+quantize,
+scalarize)
+{
+ const cases = new Array();
+ 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,
+scalar,
+op,
+quantize,
+scalarize)
+{
+ 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.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,
+scalars,
+op,
+quantize,
+scalarize)
+{
+ const cases = new Array();
+ 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,
+vectors,
+op)
+{
+ 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,
+scalars,
+op)
+{
+ 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,
+vectors,
+op)
+{
+ 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,
+scalars,
+op)
+{
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI32, i32);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_arithmetic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_arithmetic.spec.js
new file mode 100644
index 0000000000..25571a970b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_arithmetic.spec.js
@@ -0,0 +1,43 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for AbstractFloat arithmetic unary expression operations
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeAbstractFloat } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { fullF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractUnary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unary/af_arithmetic', {
+ negation: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ fullF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.abstract.negationInterval
+ );
+ }
+});
+
+g.test('negation').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: -x
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.
+combine('inputSource', onlyConstInputSource).
+combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('negation');
+ await run(t, abstractUnary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_assignment.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_assignment.spec.js
new file mode 100644
index 0000000000..777262a364
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/af_assignment.spec.js
@@ -0,0 +1,112 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 {
+ abstractFloatShaderBuilder,
+ basicExpressionBuilder,
+ onlyConstInputSource,
+ run } from
+
+'../expression.js';
+
+function concrete_assignment() {
+ return basicExpressionBuilder((value) => `${value}`);
+}
+
+function abstract_assignment() {
+ return abstractFloatShaderBuilder((value) => `${value}`);
+}
+
+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(
+ `
+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);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-conversion').
+desc(
+ `
+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);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-conversion').
+desc(
+ `
+concretizing to f16
+`
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_conversion.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_conversion.spec.js
new file mode 100644
index 0000000000..d4bbbddce4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_conversion.spec.js
@@ -0,0 +1,174 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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,
+
+ 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 } from '../expression.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 = [];
+ 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 = [];
+ 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) {
+ return vectorize === undefined ? unary('bool') : unary(`vec${vectorize}<bool>`);
+}
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+bool(e), where e is a bool
+
+Identity operation
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('bool');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeBool, t.params, cases);
+});
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#bool-builtin').
+desc(
+ `
+bool(e), where e is a u32
+
+Coercion to boolean.
+The result is false if e is 0, and true otherwise.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('u32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeBool, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+bool(e), where e is a i32
+
+Coercion to boolean.
+The result is false if e is 0, and true otherwise.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('i32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeBool, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+bool(e), where e is a f32
+
+Coercion to boolean.
+The result is false if e is 0.0 or -0.0, and true otherwise.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeBool, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+bool(e), where e is a f16
+
+Coercion to boolean.
+The result is false if e is 0.0 or -0.0, and true otherwise.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeBool, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_logical.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_logical.spec.js
new file mode 100644
index 0000000000..ac8da84846
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/bool_logical.spec.js
@@ -0,0 +1,33 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.js';
+
+import { unary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('negation').
+specURL('https://www.w3.org/TR/WGSL/#logical-expr').
+desc(
+ `
+Expression: !e
+
+Logical negation. The result is true when e is false and false when e is true. Component-wise when T is a vector.
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = [
+ { input: bool(true), expected: bool(false) },
+ { input: bool(false), expected: bool(true) }];
+
+
+ await run(t, unary('!'), [TypeBool], TypeBool, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.js
new file mode 100644
index 0000000000..e82895ffbc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.js
@@ -0,0 +1,44 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for the f16 arithmetic unary expression operations
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { TypeF16 } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { fullF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+import { allInputSources, run } from '../expression.js';
+
+import { unary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+export const d = makeCaseCache('unary/f16_arithmetic', {
+ negation: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ fullF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.f16.negationInterval
+ );
+ }
+});
+
+g.test('negation').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+Expression: -x
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('negation');
+ await run(t, unary('-'), [TypeF16], TypeF16, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_conversion.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_conversion.spec.js
new file mode 100644
index 0000000000..a37c816062
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f16_conversion.spec.js
@@ -0,0 +1,301 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../expression.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].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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) {
+ return vectorize === undefined ? unary('f16') : unary(`vec${vectorize}<f16>`);
+}
+
+/** Generate a ShaderBuilder for a matrix of the provided dimensions */
+function matrixExperession(cols, rows) {
+ return unary(`mat${cols}x${rows}<f16>`);
+}
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+f16(e), where e is a bool
+
+The result is 1.0 if e is true and 0.0 otherwise
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('bool');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF16, t.params, cases);
+});
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#bool-builtin').
+desc(
+ `
+f16(e), where e is a u32
+
+Converted to f16, +/-Inf if out of range
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+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);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+f16(e), where e is a i32
+
+Converted to f16, +/-Inf if out of range
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+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);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+f16(e), where e is a f32
+
+Correctly rounded to f16
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-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);
+});
+
+g.test('f32_mat').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f32 matrix to f16 matrix tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `f32_mat${cols}x${rows}_const` :
+ `f32_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ matrixExperession(cols, rows),
+ [TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+ f16(e), where e is a f16
+
+ Identical.
+ `
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF16, t.params, cases);
+});
+
+g.test('f16_mat').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f16 matrix to f16 matrix tests, expected identical`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `f16_mat${cols}x${rows}_const` :
+ `f16_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ matrixExperession(cols, rows),
+ [TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF16),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.js
new file mode 100644
index 0000000000..a91bab73d2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.js
@@ -0,0 +1,41 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.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(
+ `
+Expression: -x
+Accuracy: Correctly rounded
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('negation');
+ await run(t, unary('-'), [TypeF32], TypeF32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_conversion.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_conversion.spec.js
new file mode 100644
index 0000000000..d9aa209365
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/f32_conversion.spec.js
@@ -0,0 +1,257 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../expression.js';
+
+import { unary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Cases: f32_matCxR_[non_]const
+const f32_mat_cases = [2, 3, 4].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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].
+flatMap((cols) =>
+[2, 3, 4].flatMap((rows) =>
+[true, false].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) {
+ return vectorize === undefined ? unary('f32') : unary(`vec${vectorize}<f32>`);
+}
+
+/** Generate a ShaderBuilder for a matrix of the provided dimensions */
+function matrixExperession(cols, rows) {
+ return unary(`mat${cols}x${rows}<f32>`);
+}
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+f32(e), where e is a bool
+
+The result is 1.0 if e is true and 0.0 otherwise
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('bool');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF32, t.params, cases);
+});
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#bool-builtin').
+desc(
+ `
+f32(e), where e is a u32
+
+Converted to f32
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('u32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF32, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+f32(e), where e is a i32
+
+Converted to f32
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('i32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF32, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+f32(e), where e is a f32
+
+Identity operation
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF32, t.params, cases);
+});
+
+g.test('f32_mat').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f32 tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+fn(async (t) => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(
+ t.params.inputSource === 'const' ?
+ `f32_mat${cols}x${rows}_const` :
+ `f32_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ matrixExperession(cols, rows),
+ [TypeMat(cols, rows, TypeF32)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+ f32(e), where e is a f16
+
+ Expect the same value, since all f16 values is exactly representable in f32.
+ `
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF32, t.params, cases);
+});
+
+g.test('f16_mat').
+specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions').
+desc(`f16 matrix to f32 matrix tests`).
+params((u) =>
+u.
+combine('inputSource', allInputSources).
+combine('cols', [2, 3, 4]).
+combine('rows', [2, 3, 4])
+).
+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(
+ t.params.inputSource === 'const' ?
+ `f16_mat${cols}x${rows}_const` :
+ `f16_mat${cols}x${rows}_non_const`
+ );
+ await run(
+ t,
+ matrixExperession(cols, rows),
+ [TypeMat(cols, rows, TypeF16)],
+ TypeMat(cols, rows, TypeF32),
+ t.params,
+ cases
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.js
new file mode 100644
index 0000000000..9a46879c6c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.js
@@ -0,0 +1,37 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { allInputSources, run } from '../expression.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(
+ `
+Expression: -x
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('negation');
+ await run(t, unary('-'), [TypeI32], TypeI32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_complement.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_complement.spec.js
new file mode 100644
index 0000000000..e5870b2256
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_complement.spec.js
@@ -0,0 +1,37 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { 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(
+ `
+Expression: ~x
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('complement');
+ await run(t, unary('~'), [TypeI32], TypeI32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_conversion.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_conversion.spec.js
new file mode 100644
index 0000000000..a4b8247f56
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/i32_conversion.spec.js
@@ -0,0 +1,196 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../expression.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) {
+ return vectorize === undefined ? unary('i32') : unary(`vec${vectorize}<i32>`);
+}
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+i32(e), where e is a bool
+
+The result is 1u if e is true and 0u otherwise
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('bool');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeI32, t.params, cases);
+});
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#bool-builtin').
+desc(
+ `
+i32(e), where e is a u32
+
+Reinterpretation of bits
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('u32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeI32, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+i32(e), where e is a i32
+
+Identity operation
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('i32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeI32, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+i32(e), where e is a f32
+
+e is converted to i32, rounding towards zero
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeI32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+i32(e), where e is a f16
+
+e is converted to u32, rounding towards zero
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeI32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_complement.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_complement.spec.js
new file mode 100644
index 0000000000..36a664652c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_complement.spec.js
@@ -0,0 +1,37 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 { 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(
+ `
+Expression: ~x
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('complement');
+ await run(t, unary('~'), [TypeU32], TypeU32, t.params, cases);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_conversion.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_conversion.spec.js
new file mode 100644
index 0000000000..adec5bcc65
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/u32_conversion.spec.js
@@ -0,0 +1,206 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+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 } from '../expression.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) {
+ return vectorize === undefined ? unary('u32') : unary(`vec${vectorize}<u32>`);
+}
+
+g.test('bool').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+u32(e), where e is a bool
+
+The result is 1u if e is true and 0u otherwise
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('bool');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeU32, t.params, cases);
+});
+
+g.test('u32').
+specURL('https://www.w3.org/TR/WGSL/#bool-builtin').
+desc(
+ `
+u32(e), where e is a u32
+
+Identity operation
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('u32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeU32, t.params, cases);
+});
+
+g.test('i32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+u32(e), where e is a i32
+
+Reinterpretation of bits
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('i32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeU32, t.params, cases);
+});
+
+g.test('f32').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+u32(e), where e is a f32
+
+e is converted to u32, rounding towards zero
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+fn(async (t) => {
+ const cases = await d.get('f32');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeU32, t.params, cases);
+});
+
+g.test('f16').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+u32(e), where e is a f16
+
+e is converted to u32, rounding towards zero
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn(async (t) => {
+ const cases = await d.get('f16');
+ await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeU32, t.params, cases);
+});
+
+g.test('abstract_int').
+specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function').
+desc(
+ `
+u32(e), where e is an AbstractInt
+
+Identity operation if the e can be represented in u32, otherwise it produces a shader-creation error
+`
+).
+params((u) =>
+u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4])
+).
+unimplemented(); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/unary.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/unary.js
new file mode 100644
index 0000000000..914d9fed4c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/expression/unary/unary.js
@@ -0,0 +1,15 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { abstractFloatShaderBuilder, basicExpressionBuilder } from
+
+'../expression.js';
+
+/* @returns a ShaderBuilder that evaluates a prefix unary operation */
+export function unary(op) {
+ return basicExpressionBuilder((value) => `${op}(${value})`);
+}
+
+/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */
+export function abstractUnary(op) {
+ return abstractFloatShaderBuilder((value) => `${op}(${value})`);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/float_parse.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/float_parse.spec.js
new file mode 100644
index 0000000000..076c3d8a0f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/float_parse.spec.js
@@ -0,0 +1,131 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for float parsing cases
+`;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);
+
+/**
+ * Run a shader and check that the buffer output matches expectations.
+ *
+ * @param t The test object
+ * @param wgsl The shader source
+ * @param expected The array of expected values after running the shader
+ */
+function runShaderTest(t, wgsl, expected) {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef words.
+ const outputBuffer = t.makeBufferWithContents(
+ new Float32Array([...iterRange(expected.length, (_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 only the non-padding bytes were modified.
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+const kTestFloats = {
+ small_pos_zero_exp: {
+ src:
+ '0.' +
+ '00000000000000000000000000000000000000000000000000' + // 50
+ '00000000000000000000000000000000000000000000000000' + // 100
+ '00000000000000000000000000000000000000000000000000' + // 150
+ '00000000000000000000000000000000000000000000000000' + // 200
+ '00000000000000000000000000000000000000000000000000' + // 250
+ '00000000000000000000000000000000000000000000000000' + // 300
+ '00000000000000000000000000000000000000000000000000' + // 350
+ '1e+0',
+ result: 0.0
+ },
+ small_pos_non_zero_exp: {
+ src:
+ '0.' +
+ '00000000000000000000000000000000000000000000000000' + // 50
+ '00000000000000000000000000000000000000000000000000' + // 100
+ '00000000000000000000000000000000000000000000000000' + // 150
+ '00000000000000000000000000000000000000000000000000' + // 200
+ '00000000000000000000000000000000000000000000000000' + // 250
+ '00000000000000000000000000000000000000000000000000' + // 300
+ '00000000000000000000000000000000000000000000000000' + // 350
+ '1e+10',
+ result: 0.0
+ },
+ pos_exp_neg_result: {
+ src:
+ '0.' +
+ '00000000000000000000000000000000000000000000000000' + // 50
+ '00000000000000000000000000000000000000000000000000' + // 100
+ '00000000000000000000000000000000000000000000000000' + // 150
+ '00000000000000000000000000000000000000000000000000' + // 200
+ '00000000000000000000000000000000000000000000000000' + // 250
+ '00000000000000000000000000000000000000000000000000' + // 300
+ '00000000000000000000000000000000000000000000000000' + // 350
+ '1e+300',
+ result: 1e-51
+ },
+ no_exp: {
+ src:
+ '0.' +
+ '00000000000000000000000000000000000000000000000000' + // 50
+ '00000000000000000000000000000000000000000000000000' + // 100
+ '00000000000000000000000000000000000000000000000000' + // 150
+ '00000000000000000000000000000000000000000000000000' + // 200
+ '00000000000000000000000000000000000000000000000000' + // 250
+ '00000000000000000000000000000000000000000000000000' + // 300
+ '00000000000000000000000000000000000000000000000000' + // 350
+ '1',
+ result: 0.0
+ },
+ large_number_small_exp: {
+ src:
+ '1' +
+ '00000000000000000000000000000000000000000000000000' + // 50
+ '00000000000000000000000000000000000000000000000000' + // 100
+ '.0e-350',
+ result: 1e-251
+ }
+};
+
+g.test('valid').
+desc(`Test that floats are parsed correctly`).
+params((u) => u.combine('value', keysOf(kTestFloats))).
+fn((t) => {
+ const data = kTestFloats[t.params.value];
+ const wgsl = `
+ struct S {
+ val: f32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = S(${data.src});
+ }
+ `;
+ runShaderTest(t, wgsl, new Float32Array([data.result]));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/call.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/call.spec.js
new file mode 100644
index 0000000000..43897ca432
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/call.spec.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for function calls.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('call_basic').
+desc('Test that flow control enters a called function').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ f();
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn f() {
+ ${f.expect_order(1)}
+}`
+ }));
+});
+
+g.test('call_nested').
+desc('Test that flow control enters a nested function calls').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a();
+ ${f.expect_order(6)}
+`,
+ extra: `
+fn a() {
+ ${f.expect_order(1)}
+ b();
+ ${f.expect_order(5)}
+}
+fn b() {
+ ${f.expect_order(2)}
+ c();
+ ${f.expect_order(4)}
+}
+fn c() {
+ ${f.expect_order(3)}
+}`
+ }));
+});
+
+g.test('call_repeated').
+desc('Test that flow control enters a nested function calls').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a();
+ ${f.expect_order(10)}
+`,
+ extra: `
+fn a() {
+ ${f.expect_order(1)}
+ b();
+ ${f.expect_order(5)}
+ b();
+ ${f.expect_order(9)}
+}
+fn b() {
+ ${f.expect_order(2, 6)}
+ c();
+ ${f.expect_order(4, 8)}
+}
+fn c() {
+ ${f.expect_order(3, 7)}
+}`
+ }));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/complex.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/complex.spec.js
new file mode 100644
index 0000000000..33c4fa3b89
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/complex.spec.js
@@ -0,0 +1,42 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for interesting complex cases.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('continue_in_switch_in_for_loop').
+desc('Test flow control for a continue statement in a switch, in a for-loop').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < 3; i++) {
+ ${f.expect_order(1, 4, 6)}
+ switch (i) {
+ case 2: {
+ ${f.expect_order(7)}
+ break;
+ }
+ case 1: {
+ ${f.expect_order(5)}
+ continue;
+ }
+ default: {
+ ${f.expect_order(2)}
+ break;
+ }
+ }
+ ${f.expect_order(3, 8)}
+ }
+ ${f.expect_order(9)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/eval_order.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/eval_order.spec.js
new file mode 100644
index 0000000000..afba3c3dd0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/eval_order.spec.js
@@ -0,0 +1,1007 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for expression evaluation order.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('binary_op').
+desc('Test that a binary operator evaluates the LHS then the RHS').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = lhs() + rhs();
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn lhs() -> i32 {
+ ${f.expect_order(1)}
+ return 0;
+}
+fn rhs() -> i32 {
+ ${f.expect_order(2)}
+ return 0;
+}`
+ }));
+});
+
+g.test('binary_op_rhs_const').
+desc('Test that a binary operator evaluates the LHS, when the RHS is a constant expression').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = lhs() + rhs();
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn lhs() -> i32 {
+ ${f.expect_order(1)}
+ return 0;
+}
+fn rhs() -> i32 {
+ return 0;
+}`
+ }));
+});
+
+g.test('binary_op_lhs_const').
+desc('Test that a binary operator evaluates the RHS, when the LHS is a constant expression').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = lhs() + rhs();
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn lhs() -> i32 {
+ return 0;
+}
+fn rhs() -> i32 {
+ ${f.expect_order(1)}
+ return 0;
+}`
+ }));
+});
+
+g.test('binary_op_chain').
+desc('Test that a binary operator chain evaluates left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = a() + b() - c() * d();
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 1;
+}`
+ }));
+});
+
+g.test('binary_op_chain_R_C_C_C').
+desc(
+ 'Test evaluation order of a binary operator chain with a runtime-expression for the left-most expression'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = f() + 1 + 2 + 3;
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn f() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+`
+ }));
+});
+
+g.test('binary_op_chain_C_R_C_C').
+desc(
+ 'Test evaluation order of a binary operator chain with a runtime-expression for the second-left-most-const'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = 1 + f() + 2 + 3;
+ ${f.expect_order(2)}
+ `,
+ extra: `
+fn f() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+ `
+ }));
+});
+
+g.test('binary_op_chain_C_C_R_C').
+desc(
+ 'Test evaluation order of a binary operator chain with a runtime-expression for the second-right-most-const'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = 1 + 2 + f() + 3;
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn f() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+ `
+ }));
+});
+
+g.test('binary_op_chain_C_C_C_R').
+desc(
+ 'Test evaluation order of a binary operator chain with a runtime-expression for the right-most expression'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = 1 + 2 + 3 + f();
+ ${f.expect_order(2)}
+ `,
+ extra: `
+fn f() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+ `
+ }));
+});
+
+g.test('binary_op_parenthesized_expr').
+desc('Test that a parenthesized binary operator expression evaluates left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let x = (a() + b()) - (c() * d());
+ ${f.expect_order(5)}
+ let y = a() + (b() - c()) * d();
+ ${f.expect_order(10)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1, 6)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2, 7)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(3, 8)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4, 9)}
+ return 1;
+}`
+ }));
+});
+
+g.test('array_index').
+desc('Test that array indices are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<array<i32, 8>, 8>, 8>;
+ ${f.expect_order(0)}
+ let x = arr[a()][b()][c()];
+ ${f.expect_order(4)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}`
+ }));
+});
+
+g.test('array_index_lhs_assignment').
+desc(
+ 'Test that array indices are evaluated left-to-right, when indexing the LHS of an assignment'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<array<i32, 8>, 8>, 8>;
+ ${f.expect_order(0)}
+ arr[a()][b()][c()] = ~d();
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 1;
+}`
+ }));
+});
+
+g.test('array_index_lhs_member_assignment').
+desc(
+ 'Test that array indices are evaluated left-to-right, when indexing with member-accessors in the LHS of an assignment'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<S, 8>, 8>;
+ ${f.expect_order(0)}
+ arr[a()][b()].member[c()] = d();
+ ${f.expect_order(5)}
+`,
+ extra: `
+struct S {
+ member : array<i32, 8>,
+}
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 1;
+}`
+ }));
+});
+
+g.test('array_index_via_ptrs').
+desc('Test that array indices are evaluated in order, when used via pointers').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<array<i32, 8>, 8>, 8>;
+ ${f.expect_order(0)}
+ let p0 = &arr;
+ ${f.expect_order(1)}
+ let p1 = &(*p0)[a()];
+ ${f.expect_order(3)}
+ let p2 = &(*p1)[b()];
+ ${f.expect_order(5)}
+ let p3 = &(*p2)[c()];
+ ${f.expect_order(7)}
+ let p4 = *p3;
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(4)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(6)}
+ return 1;
+}`
+ }));
+});
+
+g.test('array_index_via_struct_members').
+desc('Test that array indices are evaluated in order, when accessed via structure members').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var x : X;
+ ${f.expect_order(0)}
+ let r = x.y[a()].z[b()].a[c()];
+ ${f.expect_order(4)}
+`,
+ extra: `
+struct X {
+ y : array<Y, 3>,
+};
+struct Y {
+ z : array<Z, 3>,
+};
+struct Z {
+ a : array<i32, 3>,
+};
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}`
+ }));
+});
+
+g.test('matrix_index').
+desc('Test that matrix indices are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var mat : mat4x4<f32>;
+ ${f.expect_order(0)}
+ let x = mat[a()][b()];
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}`
+ }));
+});
+
+g.test('matrix_index_via_ptr').
+desc('Test that matrix indices are evaluated in order, when used via pointers').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var mat : mat4x4<f32>;
+ ${f.expect_order(0)}
+ let p0 = &mat;
+ ${f.expect_order(1)}
+ let p1 = &(*p0)[a()];
+ ${f.expect_order(3)}
+ let v = (*p1)[b()];
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(4)}
+ return 1;
+}`
+ }));
+});
+
+g.test('logical_and').
+desc(
+ 'Test that a chain of logical-AND expressions are evaluated left-to-right, stopping at the first false'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = a() && b() && c();
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> bool {
+ ${f.expect_order(1)}
+ return true;
+}
+fn b() -> bool {
+ ${f.expect_order(2)}
+ return false;
+}
+fn c() -> bool {
+ ${f.expect_not_reached()}
+ return true;
+}
+`
+ }));
+});
+
+g.test('logical_or').
+desc(
+ 'Test that a chain of logical-OR expressions are evaluated left-to-right, stopping at the first true'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = a() || b() || c();
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> bool {
+ ${f.expect_order(1)}
+ return false;
+}
+fn b() -> bool {
+ ${f.expect_order(2)}
+ return true;
+}
+fn c() -> bool {
+ ${f.expect_not_reached()}
+ return true;
+}
+`
+ }));
+});
+
+g.test('bitwise_and').
+desc(
+ 'Test that a chain of bitwise-AND expressions are evaluated left-to-right, with no short-circuiting'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = a() & b() & c();
+ ${f.expect_order(4)}
+`,
+ extra: `
+fn a() -> bool {
+ ${f.expect_order(1)}
+ return true;
+}
+fn b() -> bool {
+ ${f.expect_order(2)}
+ return false;
+}
+fn c() -> bool {
+ ${f.expect_order(3)}
+ return true;
+}
+`
+ }));
+});
+
+g.test('bitwise_or').
+desc(
+ 'Test that a chain of bitwise-OR expressions are evaluated left-to-right, with no short-circuiting'
+).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = a() | b() | c();
+ ${f.expect_order(4)}
+`,
+ extra: `
+fn a() -> bool {
+ ${f.expect_order(1)}
+ return false;
+}
+fn b() -> bool {
+ ${f.expect_order(2)}
+ return true;
+}
+fn c() -> bool {
+ ${f.expect_order(3)}
+ return true;
+}
+`
+ }));
+});
+
+g.test('user_fn_args').
+desc('Test user function call arguments are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = f(a(), b(), c());
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 3;
+}
+fn f(x : i32, y : i32, z : i32) -> i32 {
+ ${f.expect_order(4)}
+ return x + y + z;
+}`
+ }));
+});
+
+g.test('nested_fn_args').
+desc('Test user nested call arguments are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = g(c(a(), b()), f(d(), e()));
+ ${f.expect_order(8)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 0;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 0;
+}
+fn c(x : i32, y : i32) -> i32 {
+ ${f.expect_order(3)}
+ return x + y;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 0;
+}
+fn e() -> i32 {
+ ${f.expect_order(5)}
+ return 0;
+}
+fn f(x : i32, y : i32) -> i32 {
+ ${f.expect_order(6)}
+ return x + y;
+}
+fn g(x : i32, y : i32) -> i32 {
+ ${f.expect_order(7)}
+ return x + y;
+}`
+ }));
+});
+
+g.test('builtin_fn_args').
+desc('Test builtin function call arguments are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = mix(a(), b(), c());
+ ${f.expect_order(4)}
+`,
+ extra: `
+fn a() -> f32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> f32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> f32 {
+ ${f.expect_order(3)}
+ return 3;
+}
+`
+ }));
+});
+
+g.test('nested_builtin_fn_args').
+desc('Test nested builtin function call arguments are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let l = mix(a(), mix(b(), c(), d()), e());
+ ${f.expect_order(6)}
+`,
+ extra: `
+fn a() -> f32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> f32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> f32 {
+ ${f.expect_order(3)}
+ return 3;
+}
+fn d() -> f32 {
+ ${f.expect_order(4)}
+ return 3;
+}
+fn e() -> f32 {
+ ${f.expect_order(5)}
+ return 3;
+}
+`
+ }));
+});
+
+g.test('1d_array_constructor').
+desc('Test arguments of an array constructor are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let v = array(a(), b(), c(), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('2d_array_constructor').
+desc('Test arguments of a 2D array constructor are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let v = array(array(a(), b()), array(c(), d()));
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('vec4_constructor').
+desc('Test arguments of a vector constructor are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let v = vec4(a(), b(), c(), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('nested_vec4_constructor').
+desc('Test arguments of a nested vector constructor are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let v = vec4(a(), vec2(b(), c()), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('struct_constructor').
+desc('Test arguments of a structure constructor are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let v = S(a(), b(), c(), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+struct S {
+ a : i32,
+ b : i32,
+ c : i32,
+ d : i32,
+}
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('nested_struct_constructor').
+desc('Test arguments of a nested structure constructor are evaluated left-to-right').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ let v = Y(a(), X(b(), c()), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+struct Y {
+ a : i32,
+ x : X,
+ c : i32,
+}
+struct X {
+ b : i32,
+ c : i32,
+}
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('1d_array_assignment').
+desc('Test LHS of an array element assignment is evaluated before RHS').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<i32, 8>;
+ ${f.expect_order(0)}
+ arr[a()] = arr[b()];
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('2d_array_assignment').
+desc('Test LHS of 2D-array element assignment is evaluated before RHS').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<i32, 8>, 8>;
+ ${f.expect_order(0)}
+ arr[a()][b()] = arr[c()][d()];
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('1d_array_compound_assignment').
+desc('Test LHS of an array element compound assignment is evaluated before RHS').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<i32, 8>;
+ ${f.expect_order(0)}
+ arr[a()] += arr[b()];
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('2d_array_compound_assignment').
+desc('Test LHS of a 2D-array element compound assignment is evaluated before RHS').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<i32, 8>, 8>;
+ ${f.expect_order(0)}
+ arr[a()][b()] += arr[c()][d()];
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 2;
+}
+fn c() -> i32 {
+ ${f.expect_order(3)}
+ return 1;
+}
+fn d() -> i32 {
+ ${f.expect_order(4)}
+ return 2;
+}
+`
+ }));
+});
+
+g.test('1d_array_increment').
+desc('Test index of an array element increment is evaluated only once').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<i32, 8>;
+ ${f.expect_order(0)}
+ arr[a()]++;
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+`
+ }));
+});
+
+g.test('2d_array_increment').
+desc('Test index of a 2D-array element increment is evaluated only once').
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ var arr : array<array<i32, 8>, 8>;
+ ${f.expect_order(0)}
+ arr[a()][b()]++;
+ ${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+`
+ }));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/for.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/for.spec.js
new file mode 100644
index 0000000000..182f1e999a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/for.spec.js
@@ -0,0 +1,271 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for for-loops.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('for_basic').
+desc('Test that flow control executes a for-loop body the correct number of times').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(3)}; i++) {
+ ${f.expect_order(1, 2, 3)}
+ }
+ ${f.expect_order(4)}
+`
+ );
+});
+
+g.test('for_break').
+desc('Test that flow control exits a for-loop when reaching a break statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(5)}; i++) {
+ ${f.expect_order(1, 3, 5, 7)}
+ if (i == 3) {
+ break;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2, 4, 6)}
+ }
+ ${f.expect_order(8)}
+`
+ );
+});
+
+g.test('for_continue').
+desc('Test flow control for a for-loop continue statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(5)}; i++) {
+ ${f.expect_order(1, 3, 5, 7, 8)}
+ if (i == 3) {
+ continue;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2, 4, 6, 9)}
+ }
+ ${f.expect_order(10)}
+`
+ );
+});
+
+g.test('for_initalizer').
+desc('Test flow control for a for-loop initializer').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = initializer(); i < ${f.value(3)}; i++) {
+ ${f.expect_order(2, 3, 4)}
+ }
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn initializer() -> i32 {
+ ${f.expect_order(1)}
+ return ${f.value(0)};
+}
+`
+ }));
+});
+
+g.test('for_complex_initalizer').
+desc('Test flow control for a complex for-loop initializer').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = initializer(max(a(), b())); i < ${f.value(5)}; i++) {
+ ${f.expect_order(4, 5, 6)}
+ }
+ ${f.expect_order(7)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return ${f.value(1)};
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return ${f.value(2)};
+}
+fn initializer(v : i32) -> i32 {
+ ${f.expect_order(3)}
+ return v;
+}
+`
+ }));
+});
+
+g.test('for_condition').
+desc('Test flow control for a for-loop condition').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; condition(i); i++) {
+ ${f.expect_order(2, 4, 6)}
+ }
+ ${f.expect_order(8)}
+`,
+ extra: `
+fn condition(i : i32) -> bool {
+ ${f.expect_order(1, 3, 5, 7)}
+ return i < ${f.value(3)};
+}
+`
+ }));
+});
+
+g.test('for_complex_condition').
+desc('Test flow control for a for-loop condition').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; condition(i, a() * b()); i++) {
+ ${f.expect_order(4, 8)}
+ }
+ ${f.expect_order(12)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1, 5, 9)}
+ return ${f.value(1)};
+}
+fn b() -> i32 {
+ ${f.expect_order(2, 6, 10)}
+ return ${f.value(2)};
+}
+fn condition(i : i32, j : i32) -> bool {
+ ${f.expect_order(3, 7, 11)}
+ return i < j;
+}
+`
+ }));
+});
+
+g.test('for_continuing').
+desc('Test flow control for a for-loop continuing statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(3)}; i = cont(i)) {
+ ${f.expect_order(1, 3, 5)}
+ }
+ ${f.expect_order(7)}
+`,
+ extra: `
+fn cont(i : i32) -> i32 {
+ ${f.expect_order(2, 4, 6)}
+ return i + 1;
+}
+`
+ }));
+});
+
+g.test('for_complex_continuing').
+desc('Test flow control for a for-loop continuing statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(3)}; i += cont(a(), b())) {
+ ${f.expect_order(1, 5, 9)}
+ }
+ ${f.expect_order(13)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(2, 6, 10)}
+ return ${f.value(1)};
+}
+fn b() -> i32 {
+ ${f.expect_order(3, 7, 11)}
+ return ${f.value(2)};
+}
+fn cont(i : i32, j : i32) -> i32 {
+ ${f.expect_order(4, 8, 12)}
+ return j >> u32(i);
+}
+`
+ }));
+});
+
+g.test('nested_for_break').
+desc('Test flow control for a for-loop break statement in an outer for-loop').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(2)}; i++) {
+ ${f.expect_order(1, 5)}
+ for (var i = ${f.value(5)}; i < ${f.value(7)}; i++) {
+ ${f.expect_order(2, 4, 6, 8)}
+ if (i == ${f.value(6)}) {
+ break;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(3, 7)}
+ }
+ }
+ ${f.expect_order(9)}
+`
+ );
+});
+
+g.test('nested_for_continue').
+desc('Test flow control for a for-loop continue statement in an outer for-loop').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; i < ${f.value(2)}; i++) {
+ ${f.expect_order(1, 5)}
+ for (var i = ${f.value(5)}; i < ${f.value(7)}; i++) {
+ ${f.expect_order(2, 3, 6, 7)}
+ if (i == ${f.value(5)}) {
+ continue;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(4, 8)}
+ }
+ }
+ ${f.expect_order(9)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/harness.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/harness.js
new file mode 100644
index 0000000000..bb2869833f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/harness.js
@@ -0,0 +1,312 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { Colors } from '../../../../common/util/colors.js';
+/**
+ * Options for runFlowControlTest()
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * The builder interface for the runFlowControlTest() callback.
+ * This interface is indented to be used to inject WGSL logic into the test
+ * shader.
+ * @see runFlowControlTest
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Builds, runs then checks the output of a flow control shader test.
+ *
+ * `build_wgsl` is a function that's called to build the WGSL shader.
+ * This function takes a FlowControlTestBuilder as the single argument, and
+ * returns either a string which is embedded into the WGSL entrypoint function,
+ * or an object of the signature `{ entrypoint: string; extra: string }` which
+ * contains the entrypoint code, along with additional module-scope code.
+ *
+ * The FlowControlTestBuilder should be used to insert expectations into WGSL to
+ * validate control flow. FlowControlTestBuilder also can be used to add values
+ * to the shader which cannot be optimized away.
+ *
+ * Example, testing that an if-statement behaves as expected:
+ *
+ * ```
+ * runFlowControlTest(t, f =>
+ * `
+ * ${f.expect_order(0)}
+ * if (${f.value(true)}) {
+ * ${f.expect_order(1)}
+ * } else {
+ * ${f.expect_not_reached()}
+ * }
+ * ${f.expect_order(2)}
+ * `);
+ * ```
+ *
+ * @param t The test object
+ * @param builder The shader builder function that takes a
+ * FlowControlTestBuilder as the single argument, and returns either a WGSL
+ * string which is embedded into the WGSL entrypoint function, or a structure
+ * with entrypoint-scoped WGSL code and extra module-scope WGSL code.
+ */
+export function runFlowControlTest(
+t,
+build_wgsl)
+{
+ const inputData = new Array();
+
+
+
+
+
+
+
+
+
+
+
+
+ const expectations = new Array();
+
+ const build_wgsl_result = build_wgsl({
+ value: (v) => {
+ if (t.params.preventValueOptimizations) {
+ if (typeof v === 'boolean') {
+ inputData.push(v ? 1 : 0);
+ return `inputs[${inputData.length - 1}] != 0`;
+ }
+ inputData.push(v);
+ return `inputs[${inputData.length - 1}]`;
+ } else {
+ return `${v}`;
+ }
+ },
+ expect_order: (...expected) => {
+ expectations.push({
+ kind: 'events',
+ stack: Error().stack,
+ values: expected,
+ counter: 0
+ });
+ // Expectation id starts from 1 to distinguish from initialization 0.
+ return `push_output(${expectations.length}); // expect_order(${expected.join(', ')})`;
+ },
+ expect_not_reached: () => {
+ expectations.push({
+ kind: 'not-reached',
+ stack: Error().stack
+ });
+ // Expectation id starts from 1 to distinguish from initialization 0.
+ return `push_output(${expectations.length}); // expect_not_reached()`;
+ }
+ });
+
+ const built_wgsl =
+ typeof build_wgsl_result === 'string' ?
+ { entrypoint: build_wgsl_result, extra: '' } :
+ build_wgsl_result;
+
+ const main_wgsl = built_wgsl.entrypoint !== undefined ? built_wgsl : built_wgsl.entrypoint;
+
+ const wgsl = `
+struct Outputs {
+ count : u32,
+ data : array<u32>,
+};
+@group(0) @binding(0) var<storage, read> inputs : array<i32>;
+@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
+
+fn push_output(value : u32) {
+ outputs.data[outputs.count] = value;
+ outputs.count++;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = &inputs;
+ _ = &outputs;
+ ${main_wgsl.entrypoint}
+}
+${main_wgsl.extra}
+`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ // If there are no inputs, just put a single value in the buffer to keep
+ // makeBufferWithContents() happy.
+ if (inputData.length === 0) {
+ inputData.push(0);
+ }
+
+ const inputBuffer = t.makeBufferWithContents(new Uint32Array(inputData), GPUBufferUsage.STORAGE);
+
+ 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: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } }]
+
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.eventualExpectOK(
+ t.
+ readGPUBufferRangeTyped(outputBuffer, {
+ type: Uint32Array,
+ typedLength: outputBuffer.size / 4
+ }).
+ then((outputs) => {
+ // outputs[0] is the number of outputted values
+ // outputs[1..N] holds the outputted values
+ const outputCount = outputs.data[0];
+ if (outputCount > maxOutputValues) {
+ return new Error(
+ `output data count (${outputCount}) exceeds limit of ${maxOutputValues}`
+ );
+ }
+
+ // returns an Error with the given message and WGSL source
+ const fail = (err) => Error(`${err}\nWGSL:\n${Colors.dim(Colors.blue(wgsl))}`);
+
+ // returns a string that shows the outputted values to help understand the whole trace.
+ const print_output_value = () => {
+ const subarray = outputs.data.subarray(1, outputCount + 1);
+ return `Output values (length: ${outputCount}): ${subarray.join(', ')}`;
+ };
+
+ // returns a colorized string of the expect_order() call, highlighting
+ // the event number that caused an error.
+ const expect_order_err = (expectation, err_idx) => {
+ let out = 'expect_order(';
+ for (let i = 0; i < expectation.values.length; i++) {
+ if (i > 0) {
+ out += ', ';
+ }
+ if (i < err_idx) {
+ out += Colors.green(`${expectation.values[i]}`);
+ } else if (i > err_idx) {
+ out += Colors.dim(`${expectation.values[i]}`);
+ } else {
+ out += Colors.red(`${expectation.values[i]}`);
+ }
+ }
+ out += ')';
+ return out;
+ };
+
+ // Each of the outputted values represents an event
+ // Check that each event is as expected
+ for (let event = 0; event < outputCount; event++) {
+ const eventValue = outputs.data[1 + event]; // outputs.data[0] is count
+ // Expectation id starts from 1, and 0 is invalid value.
+ if (eventValue === 0) {
+ return fail(
+ `outputs.data[${event}] is initial value 0, doesn't refer to any valid expectations)\n${print_output_value()}`
+ );
+ }
+ const expectationIndex = eventValue - 1;
+ if (expectationIndex >= expectations.length) {
+ return fail(
+ `outputs.data[${event}] value (${expectationIndex}) exceeds number of expectations (${
+ expectations.length
+ })\n${print_output_value()}`
+ );
+ }
+ const expectation = expectations[expectationIndex];
+ switch (expectation.kind) {
+ case 'not-reached':
+ return fail(
+ `expect_not_reached() reached at event ${event}\n${print_output_value()}\n${
+ expectation.stack
+ }`
+ );
+ case 'events':
+ if (expectation.counter >= expectation.values.length) {
+ return fail(
+ `${expect_order_err(
+ expectation,
+ expectation.counter
+ )}) unexpectedly reached at event ${Colors.red(
+ `${event}`
+ )}\n${print_output_value()}\n${expectation.stack}`
+ );
+ }
+ if (event !== expectation.values[expectation.counter]) {
+ return fail(
+ `${expect_order_err(expectation, expectation.counter)} expected event ${
+ expectation.values[expectation.counter]
+ }, got ${event}\n${print_output_value()}\n${expectation.stack}`
+ );
+ }
+
+ expectation.counter++;
+ break;
+ }
+ }
+
+ // Finally check that all expect_order() calls were reached
+ for (const expectation of expectations) {
+ if (expectation.kind === 'events' && expectation.counter !== expectation.values.length) {
+ return fail(
+ `${expect_order_err(expectation, expectation.counter)} event ${
+ expectation.values[expectation.counter]
+ } was not reached\n${expectation.stack}\n${print_output_value()}`
+ );
+ }
+ }
+ outputs.cleanup();
+ return undefined;
+ })
+ );
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/if.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/if.spec.js
new file mode 100644
index 0000000000..4f48b2beb3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/if.spec.js
@@ -0,0 +1,102 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for if-statements.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('if_true').
+desc(
+ "Test that flow control executes the 'true' block of an if statement and not the 'false' block"
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ if (${f.value(true)}) {
+ ${f.expect_order(1)}
+ } else {
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2)}
+`
+ );
+});
+
+g.test('if_false').
+desc(
+ "Test that flow control executes the 'false' block of an if statement and not the 'true' block"
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ if (${f.value(false)}) {
+ ${f.expect_not_reached()}
+ } else {
+ ${f.expect_order(1)}
+ }
+ ${f.expect_order(2)}
+`
+ );
+});
+
+g.test('else_if').
+desc("Test that flow control executes the correct 'else if' block of an if statement").
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ if (${f.value(false)}) {
+ ${f.expect_not_reached()}
+ } else if (${f.value(false)}) {
+ ${f.expect_not_reached()}
+ } else if (${f.value(true)}) {
+ ${f.expect_order(1)}
+ } else if (${f.value(false)}) {
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2)}
+`
+ );
+});
+
+g.test('nested_if_else').
+desc('Test flow control for nested if-else statements').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+${f.expect_order(0)}
+if (${f.value(true)}) {
+ ${f.expect_order(1)}
+ if (${f.value(false)}) {
+ ${f.expect_not_reached()}
+ } else {
+ ${f.expect_order(2)}
+ if (${f.value(true)}) {
+ ${f.expect_order(3)}
+ } else {
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(4)}
+ }
+ ${f.expect_order(5)}
+} else {
+ ${f.expect_not_reached()}
+}
+${f.expect_order(6)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/loop.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/loop.spec.js
new file mode 100644
index 0000000000..adb5e0916d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/loop.spec.js
@@ -0,0 +1,125 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for loops.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('loop_break').
+desc('Test that flow control exits a loop when reaching a break statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 3, 5, 7)}
+ if i == 3 {
+ break;
+ }
+ ${f.expect_order(2, 4, 6)}
+ i++;
+ }
+ ${f.expect_order(8)}
+`
+ );
+});
+
+g.test('loop_continue').
+desc('Test flow control for a loop continue statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 3, 5, 7, 8)}
+ if i == 3 {
+ i++;
+ continue;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2, 4, 6, 9)}
+ if i == 4 {
+ break;
+ }
+ i++;
+ }
+ ${f.expect_order(10)}
+`
+ );
+});
+
+g.test('loop_continuing_basic').
+desc('Test basic flow control for a loop continuing block').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 3, 5)}
+ i++;
+
+ continuing {
+ ${f.expect_order(2, 4, 6)}
+ break if i == 3;
+ }
+ }
+ ${f.expect_order(7)}
+`
+ );
+});
+
+g.test('nested_loops').
+desc('Test flow control for a loop nested in another loop').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 11, 21)}
+ if i == ${f.value(6)} {
+ ${f.expect_order(22)}
+ break;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2, 12)}
+ loop {
+ i++;
+ ${f.expect_order(3, 6, 9, 13, 16, 19)}
+ if (i % ${f.value(3)}) == 0 {
+ ${f.expect_order(10, 20)}
+ break;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(4, 7, 14, 17)}
+ if (i & ${f.value(1)}) == 0 {
+ ${f.expect_order(8, 15)}
+ continue;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(5, 18)}
+ }
+ }
+ ${f.expect_order(23)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/phony.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/phony.spec.js
new file mode 100644
index 0000000000..a21698b57f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/phony.spec.js
@@ -0,0 +1,135 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for phony assignments.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('phony_assign_call_basic').
+desc('Test flow control for a phony assigned with a single function call').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ _ = f();
+ ${f.expect_order(2)}
+`,
+ extra: `
+fn f() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+`
+ }));
+});
+
+g.test('phony_assign_call_must_use').
+desc(
+ 'Test flow control for a phony assigned with a single function call annotated with @must_use'
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ _ = f();
+ ${f.expect_order(2)}
+`,
+ extra: `
+@must_use
+fn f() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+`
+ }));
+});
+
+g.test('phony_assign_call_nested').
+desc('Test flow control for a phony assigned with nested function calls').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+${f.expect_order(0)}
+_ = c(a(), b());
+${f.expect_order(4)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+fn c(x : i32, y : i32) -> i32 {
+ ${f.expect_order(3)}
+ return x + y;
+}
+`
+ }));
+});
+
+g.test('phony_assign_call_nested_must_use').
+desc(
+ 'Test flow control for a phony assigned with nested function calls, all annotated with @must_use'
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+${f.expect_order(0)}
+_ = c(a(), b());
+${f.expect_order(4)}
+`,
+ extra: `
+@must_use
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+@must_use
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+@must_use
+fn c(x : i32, y : i32) -> i32 {
+ ${f.expect_order(3)}
+ return x + y;
+}
+`
+ }));
+});
+
+g.test('phony_assign_call_builtin').
+desc(
+ 'Test flow control for a phony assigned with a builtin call, with two function calls as arguments'
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(t, (f) => ({
+ entrypoint: `
+${f.expect_order(0)}
+_ = max(a(), b());
+${f.expect_order(3)}
+`,
+ extra: `
+fn a() -> i32 {
+ ${f.expect_order(1)}
+ return 1;
+}
+fn b() -> i32 {
+ ${f.expect_order(2)}
+ return 1;
+}
+`
+ }));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/return.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/return.spec.js
new file mode 100644
index 0000000000..270e75845e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/return.spec.js
@@ -0,0 +1,56 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for return statements.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('return').
+desc("Test that flow control does not execute after a 'return' statement").
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ return;
+ ${f.expect_not_reached()}
+`
+ );
+});
+
+g.test('return_conditional_true').
+desc("Test that flow control does not execute after a 'return' statement in a if (true) block").
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ if (${f.value(true)}) {
+ return;
+ }
+ ${f.expect_not_reached()}
+`
+ );
+});
+
+g.test('return_conditional_false').
+desc("Test that flow control does not execute after a 'return' statement in a if (false) block").
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ if (${f.value(false)}) {
+ return;
+ }
+ ${f.expect_order(1)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/switch.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/switch.spec.js
new file mode 100644
index 0000000000..09664a067e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/switch.spec.js
@@ -0,0 +1,156 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for switch statements.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('switch').
+desc('Test that flow control executes the correct switch case block').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ switch (${f.value(1)}) {
+ case 0: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ case 1: {
+ ${f.expect_order(1)}
+ break;
+ }
+ case 2: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ default: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ }
+ ${f.expect_order(2)}
+`
+ );
+});
+
+g.test('switch_multiple_case').
+desc(
+ 'Test that flow control executes the correct switch case block with multiple cases per block'
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ switch (${f.value(2)}) {
+ case 0, 1: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ case 2, 3: {
+ ${f.expect_order(1)}
+ break;
+ }
+ default: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ }
+ ${f.expect_order(2)}
+`
+ );
+});
+
+g.test('switch_multiple_case_default').
+desc(
+ 'Test that flow control executes the correct switch case block with multiple cases per block (combined with default)'
+).
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+ ${f.expect_order(0)}
+ switch (${f.value(2)}) {
+ case 0, 1: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ case 2, 3, default: {
+ ${f.expect_order(1)}
+ break;
+ }
+ }
+ ${f.expect_order(2)}
+ switch (${f.value(1)}) {
+ case 0, 1: {
+ ${f.expect_order(3)}
+ break;
+ }
+ case 2, 3, default: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ }
+ ${f.expect_order(4)}
+`
+ );
+});
+
+g.test('switch_default').
+desc('Test that flow control executes the switch default block').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+${f.expect_order(0)}
+switch (${f.value(4)}) {
+ case 0: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ case 1: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ case 2: {
+ ${f.expect_not_reached()}
+ break;
+ }
+ default: {
+ ${f.expect_order(1)}
+ break;
+ }
+}
+${f.expect_order(2)}
+`
+ );
+});
+
+g.test('switch_default_only').
+desc('Test that flow control executes the switch default block, which is the only case').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) => `
+${f.expect_order(0)}
+switch (${f.value(4)}) {
+default: {
+ ${f.expect_order(1)}
+ break;
+}
+}
+${f.expect_order(2)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/while.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/while.spec.js
new file mode 100644
index 0000000000..f4bbc942ef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/flow_control/while.spec.js
@@ -0,0 +1,140 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Flow control tests for while-loops.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import { runFlowControlTest } from './harness.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('while_basic').
+desc('Test that flow control executes a while-loop body the correct number of times').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (i < ${f.value(5)}) {
+ ${f.expect_order(1, 2, 3, 4, 5)}
+ i++;
+ }
+ ${f.expect_order(6)}
+`
+ );
+});
+
+g.test('while_break').
+desc('Test that flow control exits a while-loop when reaching a break statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (i < ${f.value(5)}) {
+ ${f.expect_order(1, 3, 5, 7)}
+ if (i == 3) {
+ break;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2, 4, 6)}
+ i++;
+ }
+ ${f.expect_order(8)}
+`
+ );
+});
+
+g.test('while_continue').
+desc('Test flow control for a while-loop continue statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (i < ${f.value(5)}) {
+ ${f.expect_order(1, 3, 5, 7, 8)}
+ if (i == 3) {
+ i++;
+ continue;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(2, 4, 6, 9)}
+ i++;
+ }
+ ${f.expect_order(10)}
+`
+ );
+});
+
+g.test('while_nested_break').
+desc('Test that flow control exits a nested while-loop when reaching a break statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (i < ${f.value(3)}) {
+ ${f.expect_order(1, 5, 11)}
+ i++;
+ var j = ${f.value(0)};
+ while (j < i) {
+ ${f.expect_order(2, 6, 8, 12)}
+ j++;
+ if ((i+j) & 2) == 0 {
+ ${f.expect_order(9, 13)}
+ break;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(3, 7)}
+ }
+ ${f.expect_order(4, 10, 14)}
+ }
+ ${f.expect_order(15)}
+`
+ );
+});
+
+g.test('while_nested_continue').
+desc('Test flow control for a nested while-loop with a continue statement').
+params((u) => u.combine('preventValueOptimizations', [true, false])).
+fn((t) => {
+ runFlowControlTest(
+ t,
+ (f) =>
+ `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (i < ${f.value(3)}) {
+ ${f.expect_order(1, 5, 11)}
+ i++;
+ var j = ${f.value(0)};
+ while (j < i) {
+ ${f.expect_order(2, 6, 8, 12, 14, 16)}
+ j++;
+ if ((i+j) & 2) == 0 {
+ ${f.expect_order(9, 13, 15)}
+ continue;
+ ${f.expect_not_reached()}
+ }
+ ${f.expect_order(3, 7, 17)}
+ }
+ ${f.expect_order(4, 10, 18)}
+ }
+ ${f.expect_order(19)}
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/adjacent.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/adjacent.spec.js
new file mode 100644
index 0000000000..c049664e86
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/adjacent.spec.js
@@ -0,0 +1,272 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests writes from different invocations to adjacent scalars do not interfere.
+This is especially interesting when the scalar type is narrower than 32-bits.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { PRNG } from '../../../util/prng.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Algorithm: with N invocations, N is even:
+// srcBuffer: An array of random scalar values. Avoids unsupported values like infinity and NaN.
+// resultBuffer: A result array
+// pattern: 0|1|2|3
+// Pattern 0: Identity: invocation i: dst[i] = src[i]
+// Pattern 1: Try to prevent write coalescing.
+// Even elements stay in place.
+// Reverse order of odd elements.
+// invocation 2k: dst[2k] = src[2k]
+// invocation 2k+1: dst[2k+1] = src[N - (2k+1)]
+// Example: with N=6
+// dst[0] = src[0]
+// dst[1] = src[5]
+// dst[2] = src[2]
+// dst[3] = src[3]
+// dst[4] = src[4]
+// dst[5] = src[1]
+// Pattern 2: Try to prevent write coalescing.
+// Reverse order of even elements.
+// Odd elements stay in place.
+// invocation 2k: dst[2k] = src[N - 2 - 2k]
+// invocation 2k+1: dst[2k+1] = src[2k+1]
+// Example: with N=6
+// dst[0] = src[4]
+// dst[1] = src[1]
+// dst[2] = src[2]
+// dst[3] = src[3]
+// dst[4] = src[0]
+// dst[5] = src[5]
+// Pattern 3: Reverse elements: dst[i] = src[N-1-i]
+// addressSpace: workgroup|storage
+// Where dst is allocated.
+
+
+
+const kAddressSpaces = ['workgroup', 'storage'];
+const kPatterns = [0, 1, 2, 3];
+
+
+
+
+
+
+
+
+// For simplicity, make the entire source (and destination) array fit
+// in workgroup memory.
+// We can count on up to 16384 bytes in workgroup memory.
+const kNumValues = 4096; // Assumed even
+const kWorkgroupSize = 128; // Use 1-dimensional workgroups.
+
+/**
+ * @returns an integer for the bit pattern of a random finite f16 value.
+ * Consumes values from `prng`.
+ *
+ * @param prng - a pseudo-random number generator.
+ */
+function randomFiniteF16(prng) {
+ const exponent_bits = 0x7c00;
+ // With any reasonable random number stream, the average number
+ // of trips around this loop is < 1 + 1/32 because there are 5
+ // exponent bits.
+ let candidate;
+ do {
+ candidate = prng.randomU32() & 0xffff;
+ // Non-finite f16 values have all 1 bits in the exponent.
+ } while ((candidate & exponent_bits) === exponent_bits);
+ return candidate;
+}
+
+/**
+ * Fills array `arr` with random finite f16 values.
+ * Consumes values from `prng`.
+ *
+ * @param prng - a pseudo-random number generator.
+ * @param arr - the array to fill. Assume it is already correctly sized.
+ */
+function fillWithRandomFiniteF16(prng, arr) {
+ for (let i = 0; i < arr.length; i++) {
+ arr[i] = randomFiniteF16(prng);
+ }
+}
+
+/**
+ * @returns the expression for the destination index, based on `pattern`.
+ *
+ * @param i the WGSL string for the source index
+ * @param pattern the indexing pattern
+ */
+function getDstIndexExpression(i, pattern) {
+ switch (pattern) {
+ case 0:
+ return `${i}`;
+ case 1:
+ // Even elements map to themselves.
+ // Odd elements map to the reversed order of odd elements.
+ return `select(${kNumValues} - ${i}, ${i}, (${i} & 1) == 0)`;
+ case 2:
+ // Even elements map to the reversed order of odd elements.
+ // Since N is even, element 0 should get index N-2. (!)
+ // Odd elements map to themselves.
+ return `select(${i}, ${kNumValues} - 2 - ${i}, (${i} & 1) == 0)`;
+ case 3:
+ return `${kNumValues} - 1 -${i}`;
+ }
+}
+
+/**
+ * Computes the reference (correct) result for the given source array and indexing pattern.
+ *
+ * @param pattern the indexing pattern
+ * @param src the source array
+ * @param dst the array to fill with values transferred from `src`
+ */
+function computeReference(pattern, src, dst) {
+ for (let i = 0; i < src.length; i++) {
+ const isEven = (i & 1) === 0;
+ switch (pattern) {
+ case 0:
+ dst[i] = src[i];
+ break;
+ case 1:
+ if (isEven) {
+ dst[i] = src[i];
+ } else {
+ dst[src.length - i] = src[i];
+ }
+ break;
+ case 2:
+ if (isEven) {
+ dst[kNumValues - 2 - i] = src[i];
+ } else {
+ dst[i] = src[i];
+ }
+ break;
+ case 3:
+ dst[src.length - 1 - i] = src[i];
+ break;
+ }
+ }
+}
+
+/**
+ * @returns the source text for a shader that copies elements from a source
+ * buffer to a destination buffer, while remapping indices according to the
+ * specified pattern.
+ *
+ * @param p contains the address space and pattern
+ */
+function makeShaderText(p) {
+ // When the destination buffer is in 'storage', then write directly to it.
+ // Otherwise, destination is in workgroup memory, and we need to name the
+ // output buffer differently.
+ const dstBuf = p.addressSpace === 'storage' ? 'dst' : 'dstBuf';
+
+ const parts = [];
+
+ parts.push(`
+ enable f16;
+ @group(0) @binding(0) var<storage> src: array<f16>;
+ @group(0) @binding(1) var<storage,read_write> ${dstBuf}: array<f16>;
+ `);
+
+ if (p.addressSpace === 'workgroup') {
+ parts.push(`var<workgroup> dst: array<f16,${kNumValues}>;`);
+ }
+
+ parts.push(`
+ @compute @workgroup_size(${kWorkgroupSize})
+ fn adjacent_writes(@builtin(global_invocation_id) gid: vec3u) {
+ let srcIndex = gid.x;
+ let dstIndex = ${getDstIndexExpression('srcIndex', p.pattern)};
+ dst[dstIndex] = src[srcIndex];
+ `);
+
+ if (p.addressSpace === 'workgroup') {
+ // Copy to the output buffer.
+ // The barrier is not necessary here, but it should prevent
+ // the compiler from being clever and optimizing away the
+ // intermediate write to workgroup memory.
+ parts.push(` workgroupBarrier();`);
+ parts.push(` ${dstBuf}[dstIndex] = dst[dstIndex];`);
+ }
+ parts.push('}');
+
+ return parts.join('\n');
+}
+
+/**
+ * Runs the test on the GPU, generating random source data and
+ * checking the results against the expected permutation of that data.
+ *
+ * @param t the AdjacentWritesTest specification.
+ */
+function runTest(t) {
+ const seed = (t.params.pattern + 1) * t.params.addressSpace.length;
+ const prng = new PRNG(seed);
+
+ const expected = new Uint16Array(kNumValues);
+
+ const bytesPerScalar = 2; // f16 is 2 bytes wide.
+ const bufByteSize = kNumValues * bytesPerScalar;
+ const hostSrcBuf = t.device.createBuffer({
+ size: bufByteSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
+ mappedAtCreation: true
+ });
+ {
+ const hostSrcUint16 = new Uint16Array(hostSrcBuf.getMappedRange());
+ fillWithRandomFiniteF16(prng, hostSrcUint16);
+ computeReference(t.params.pattern, hostSrcUint16, expected);
+ hostSrcBuf.unmap();
+ }
+
+ const srcBuf = t.device.createBuffer({
+ size: bufByteSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ });
+ const dstBuf = t.device.createBuffer({
+ size: bufByteSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+
+ const shaderText = makeShaderText(t.params);
+ const shader = t.device.createShaderModule({ code: shaderText });
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: { module: shader, entryPoint: 'adjacent_writes' }
+ });
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: srcBuf } },
+ { binding: 1, resource: { buffer: dstBuf } }]
+
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToBuffer(hostSrcBuf, 0, srcBuf, 0, bufByteSize);
+
+ const computeEncoder = encoder.beginComputePass();
+ computeEncoder.setPipeline(pipeline);
+ computeEncoder.setBindGroup(0, bindGroup);
+ computeEncoder.dispatchWorkgroups(kNumValues / kWorkgroupSize);
+ computeEncoder.end();
+
+ const commands = encoder.finish();
+ t.device.queue.submit([commands]);
+
+ t.expectGPUBufferValuesEqual(dstBuf, expected);
+}
+
+g.test('f16').
+desc(
+ `Check that writes by different invocations to adjacent f16 values in an array do not interfere with each other.`
+).
+params((u) => u.combine('addressSpace', kAddressSpaces).combine('pattern', kPatterns)).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => runTest(t)); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/atomicity.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/atomicity.spec.js
new file mode 100644
index 0000000000..175948859d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/atomicity.spec.js
@@ -0,0 +1,102 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Tests for the atomicity of atomic read-modify-write instructions.`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import {
+
+ MemoryModelTester,
+ buildTestShader,
+ TestType,
+ buildResultShader,
+ ResultType,
+ MemoryType } from
+'./memory_model_setup.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// A reasonable parameter set, determined heuristically.
+const memoryModelTestParams = {
+ workgroupSize: 256,
+ testingWorkgroups: 512,
+ maxWorkgroups: 1024,
+ shufflePct: 100,
+ barrierPct: 100,
+ memStressPct: 100,
+ memStressIterations: 1024,
+ memStressStoreFirstPct: 50,
+ memStressStoreSecondPct: 50,
+ preStressPct: 100,
+ preStressIterations: 1024,
+ preStressStoreFirstPct: 50,
+ preStressStoreSecondPct: 50,
+ scratchMemorySize: 2048,
+ stressLineSize: 64,
+ stressTargetLines: 2,
+ stressStrategyBalancePct: 50,
+ permuteFirst: 109,
+ permuteSecond: 419,
+ memStride: 4,
+ aliasedMemory: false,
+ numBehaviors: 4
+};
+
+const storageMemoryTestCode = `
+ let r0 = atomicAdd(&test_locations.value[x_0], 0u);
+ atomicStore(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[id_0].r0, r0);
+`;
+
+const workgroupMemoryTestCode = `
+ let r0 = atomicAdd(&wg_test_locations[x_0], 0u);
+ atomicStore(&wg_test_locations[x_1], 2u);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+`;
+
+const resultCode = `
+ if ((r0 == 0u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 2u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 0u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+`;
+
+g.test('atomicity').
+desc(
+ `Checks whether a store on one thread can interrupt an atomic RMW on a second thread. If the read returned by
+ the RMW instruction is the initial value of memory (0), but the final value in memory is 1, then the atomic write
+ in the second thread occurred in between the read and the write of the RMW.
+ `
+).
+paramsSimple([
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: storageMemoryTestCode
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryTestCode
+}]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(t.params._testCode, t.params.memType, t.params.testType);
+ const resultShader = buildResultShader(resultCode, t.params.testType, ResultType.FourBehavior);
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader
+ );
+ await memModelTester.run(10, 3);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/barrier.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/barrier.spec.js
new file mode 100644
index 0000000000..f5e41440bb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/barrier.spec.js
@@ -0,0 +1,250 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for non-atomic memory synchronization within a workgroup in the presence of a WebGPU barrier`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import {
+
+ MemoryModelTester,
+ kAccessValueTypes,
+ buildTestShader,
+ MemoryType,
+ TestType,
+ buildResultShader,
+ ResultType } from
+'./memory_model_setup.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// A reasonable parameter set, determined heuristically.
+const memoryModelTestParams = {
+ workgroupSize: 256,
+ testingWorkgroups: 512,
+ maxWorkgroups: 1024,
+ shufflePct: 100,
+ barrierPct: 100,
+ memStressPct: 100,
+ memStressIterations: 1024,
+ memStressStoreFirstPct: 50,
+ memStressStoreSecondPct: 50,
+ preStressPct: 100,
+ preStressIterations: 1024,
+ preStressStoreFirstPct: 50,
+ preStressStoreSecondPct: 50,
+ scratchMemorySize: 2048,
+ stressLineSize: 64,
+ stressTargetLines: 2,
+ stressStrategyBalancePct: 50,
+ permuteFirst: 109,
+ permuteSecond: 419,
+ memStride: 4,
+ aliasedMemory: false,
+ numBehaviors: 2
+};
+
+// The two kinds of non-atomic accesses tested.
+// rw: read -> barrier -> write
+// wr: write -> barrier -> read
+// ww: write -> barrier -> write
+
+
+// Test the non-atomic memory types.
+const kMemTypes = [MemoryType.NonAtomicStorageClass, MemoryType.NonAtomicWorkgroupClass];
+
+const storageMemoryBarrierStoreLoadTestCode = `
+ test_locations.value[x_0] = 1;
+ workgroupBarrier();
+ let r0 = u32(test_locations.value[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
+const workgroupMemoryBarrierStoreLoadTestCode = `
+ wg_test_locations[x_0] = 1;
+ workgroupBarrier();
+ 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();
+ test_locations.value[x_1] = 1;
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+const workgroupMemoryBarrierLoadStoreTestCode = `
+ let r0 = u32(wg_test_locations[x_0]);
+ workgroupBarrier();
+ 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 workgroupMemoryBarrierStoreStoreTestCode = `
+ wg_test_locations[x_0] = 1;
+ workgroupBarrier();
+ wg_test_locations[x_1] = 2;
+ workgroupBarrier();
+ test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1] = wg_test_locations[x_1];
+`;
+
+function getTestCode(p) {
+ 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;
+ }
+}
+
+g.test('workgroup_barrier_store_load').
+desc(
+ `Checks whether the workgroup barrier properly synchronizes a non-atomic write and read on
+ separate threads in the same workgroup. Within a workgroup, the barrier should force an invocation
+ after the barrier to read a write from an invocation before the barrier.
+ `
+).
+params((u) =>
+u.
+combine('accessValueType', kAccessValueTypes).
+combine('memType', kMemTypes).
+combine('accessPair', ['wr'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.accessValueType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn(async (t) => {
+ const resultCode = `
+ if (r0 == 1u) {
+ atomicAdd(&test_results.seq, 1u);
+ } else if (r0 == 0u) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(
+ getTestCode(t.params),
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const resultShader = buildResultShader(
+ resultCode,
+ TestType.IntraWorkgroup,
+ ResultType.TwoBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader,
+ t.params.accessValueType
+ );
+ await memModelTester.run(15, 1);
+});
+
+g.test('workgroup_barrier_load_store').
+desc(
+ `Checks whether the workgroup barrier properly synchronizes a non-atomic write and read on
+ separate threads in the same workgroup. Within a workgroup, the barrier should force an invocation
+ before the barrier to not read the write from an invocation after the barrier.
+ `
+).
+params((u) =>
+u.
+combine('accessValueType', kAccessValueTypes).
+combine('memType', kMemTypes).
+combine('accessPair', ['rw'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.accessValueType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn(async (t) => {
+ const resultCode = `
+ if (r0 == 0u) {
+ atomicAdd(&test_results.seq, 1u);
+ } else if (r0 == 1u) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(
+ getTestCode(t.params),
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const resultShader = buildResultShader(
+ resultCode,
+ TestType.IntraWorkgroup,
+ ResultType.TwoBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader,
+ t.params.accessValueType
+ );
+ await memModelTester.run(12, 1);
+});
+
+g.test('workgroup_barrier_store_store').
+desc(
+ `Checks whether the workgroup barrier properly synchronizes non-atomic writes on
+ separate threads in the same workgroup. Within a workgroup, the barrier should force the value in memory
+ to be the result of the write after the barrier, not the write before.
+ `
+).
+params((u) =>
+u.
+combine('accessValueType', kAccessValueTypes).
+combine('memType', kMemTypes).
+combine('accessPair', ['ww'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.accessValueType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn(async (t) => {
+ const resultCode = `
+ if (mem_x_0 == 2u) {
+ atomicAdd(&test_results.seq, 1u);
+ } else if (mem_x_0 == 1u) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(
+ getTestCode(t.params),
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const resultShader = buildResultShader(
+ resultCode,
+ TestType.IntraWorkgroup,
+ ResultType.TwoBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader,
+ t.params.accessValueType
+ );
+ await memModelTester.run(10, 1);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/coherence.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/coherence.spec.js
new file mode 100644
index 0000000000..725bf4cf00
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/coherence.spec.js
@@ -0,0 +1,525 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests that all threads see a sequentially consistent view of the order of memory
+accesses to a single memory location. Uses a parallel testing strategy along with stressing
+threads to increase coverage of possible bugs.`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import {
+
+ MemoryModelTester,
+ buildTestShader,
+ MemoryType,
+ TestType,
+ buildResultShader,
+ ResultType } from
+'./memory_model_setup.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// A reasonable parameter set, determined heuristically.
+const memoryModelTestParams = {
+ workgroupSize: 256,
+ testingWorkgroups: 39,
+ maxWorkgroups: 952,
+ shufflePct: 0,
+ barrierPct: 0,
+ memStressPct: 0,
+ memStressIterations: 1024,
+ memStressStoreFirstPct: 50,
+ memStressStoreSecondPct: 50,
+ preStressPct: 0,
+ preStressIterations: 1024,
+ preStressStoreFirstPct: 50,
+ preStressStoreSecondPct: 50,
+ scratchMemorySize: 2048,
+ stressLineSize: 64,
+ stressTargetLines: 2,
+ stressStrategyBalancePct: 50,
+ permuteFirst: 109,
+ permuteSecond: 1,
+ memStride: 1,
+ aliasedMemory: true,
+ numBehaviors: 4
+};
+
+const storageMemoryCorrTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[x_1]);
+ let r1 = atomicLoad(&test_locations.value[y_1]);
+ atomicStore(&results.value[id_1].r0, r0);
+ atomicStore(&results.value[id_1].r1, r1);
+`;
+
+const workgroupStorageMemoryCorrTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[x_1]);
+ let r1 = atomicLoad(&test_locations.value[y_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+const storageMemoryCorrRMWTestCode = `
+ atomicExchange(&test_locations.value[x_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[x_1]);
+ let r1 = atomicAdd(&test_locations.value[y_1], 0u);
+ atomicStore(&results.value[id_1].r0, r0);
+ atomicStore(&results.value[id_1].r1, r1);
+`;
+
+const workgroupStorageMemoryCorrRMWTestCode = `
+ atomicExchange(&test_locations.value[x_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[x_1]);
+ let r1 = atomicAdd(&test_locations.value[y_1], 0u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+const workgroupMemoryCorrTestCode = `
+ atomicStore(&wg_test_locations[x_0], 1u);
+ let r0 = atomicLoad(&wg_test_locations[x_1]);
+ let r1 = atomicLoad(&wg_test_locations[y_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+const workgroupMemoryCorrRMWTestCode = `
+ atomicExchange(&wg_test_locations[x_0], 1u);
+ let r0 = atomicLoad(&wg_test_locations[x_1]);
+ let r1 = atomicAdd(&wg_test_locations[y_1], 0u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+g.test('corr').
+desc(
+ `Ensures two reads on one thread cannot observe an inconsistent view of a write on a second thread.
+ The first thread writes the value 1 some location x, and the second thread reads x twice in a row.
+ If the first read returns 1 but the second read returns 0, then there has been a coherence violation.
+ `
+).
+paramsSimple([
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCorrTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCorrRMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCorrTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCorrRMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCorrTestCode
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCorrRMWTestCode,
+ extraFlags: 'rmw_variant'
+}]
+).
+fn(async (t) => {
+ const resultCode = `
+ if ((r0 == 0u && r1 == 0u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 1u && r1 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 0u && r1 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 1u && r1 == 0u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(t.params._testCode, t.params.memType, t.params.testType);
+ const resultShader = buildResultShader(resultCode, t.params.testType, ResultType.FourBehavior);
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader
+ );
+ await memModelTester.run(60, 3);
+});
+
+const storageMemoryCowwTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ atomicStore(&test_locations.value[y_0], 2u);
+`;
+
+const storageMemoryCowwRMWTestCode = `
+ atomicExchange(&test_locations.value[x_0], 1u);
+ atomicStore(&test_locations.value[y_0], 2u);
+`;
+
+const workgroupMemoryCowwTestCode = `
+ atomicStore(&wg_test_locations[x_0], 1u);
+ atomicStore(&wg_test_locations[y_0], 2u);
+ workgroupBarrier();
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_0], atomicLoad(&wg_test_locations[x_0]));
+`;
+
+const workgroupMemoryCowwRMWTestCode = `
+ atomicExchange(&wg_test_locations[x_0], 1u);
+ atomicStore(&wg_test_locations[y_0], 2u);
+ workgroupBarrier();
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_0], atomicLoad(&wg_test_locations[x_0]));
+`;
+
+g.test('coww').
+desc(
+ `Ensures two writes on one thread do not lead to incoherent results. The thread first writes 1 to
+ some location x and then writes 2 to the same location. If the value in memory after the test finishes
+ is 1, then there has been a coherence violation.
+ `
+).
+paramsSimple([
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCowwTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCowwRMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: storageMemoryCowwTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: storageMemoryCowwRMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCowwTestCode
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCowwRMWTestCode,
+ extraFlags: 'rmw_variant'
+}]
+).
+fn(async (t) => {
+ const resultCode = `
+ if (mem_x_0 == 2u) {
+ atomicAdd(&test_results.seq, 1u);
+ } else if (mem_x_0 == 1u) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(t.params._testCode, t.params.memType, t.params.testType);
+ const resultShader = buildResultShader(resultCode, t.params.testType, ResultType.TwoBehavior);
+ const params = {
+ ...memoryModelTestParams,
+ numBehaviors: 2
+ };
+ const memModelTester = new MemoryModelTester(t, params, testShader, resultShader);
+ await memModelTester.run(60, 1);
+});
+
+const storageMemoryCowrTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[y_0]);
+ atomicStore(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[id_0].r0, r0);
+`;
+
+const workgroupStorageMemoryCowrTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[y_0]);
+ atomicStore(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+const storageMemoryCowrRMWTestCode = `
+ atomicExchange(&test_locations.value[x_0], 1u);
+ let r0 = atomicAdd(&test_locations.value[y_0], 0u);
+ atomicExchange(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[id_0].r0, r0);
+`;
+
+const workgroupStorageMemoryCowrRMWTestCode = `
+ atomicExchange(&test_locations.value[x_0], 1u);
+ let r0 = atomicAdd(&test_locations.value[y_0], 0u);
+ atomicExchange(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+const workgroupMemoryCowrTestCode = `
+ atomicStore(&wg_test_locations[x_0], 1u);
+ let r0 = atomicLoad(&wg_test_locations[y_0]);
+ atomicStore(&wg_test_locations[x_1], 2u);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+`;
+
+const workgroupMemoryCowrRMWTestCode = `
+ atomicExchange(&wg_test_locations[x_0], 1u);
+ let r0 = atomicAdd(&wg_test_locations[y_0], 0u);
+ atomicExchange(&wg_test_locations[x_1], 2u);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+`;
+
+g.test('cowr').
+desc(
+ `The first thread first writes 1 to some location x and then reads x. The second thread writes 2 to x.
+ If the first thread reads the value 2 and the value in memory at the end of the test is 1, then the read
+ and write on the first thread have been reordered, a coherence violation.
+ `
+).
+paramsSimple([
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCowrTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCowrRMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCowrTestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCowrRMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCowrTestCode
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCowrRMWTestCode,
+ extraFlags: 'rmw_variant'
+}]
+).
+fn(async (t) => {
+ const resultCode = `
+ if ((r0 == 1u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 1u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 2u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 2u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(t.params._testCode, t.params.memType, t.params.testType);
+ const resultShader = buildResultShader(resultCode, t.params.testType, ResultType.FourBehavior);
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader
+ );
+ await memModelTester.run(60, 3);
+});
+
+const storageMemoryCorw1TestCode = `
+ let r0 = atomicLoad(&test_locations.value[x_0]);
+ atomicStore(&test_locations.value[x_0], 1u);
+ workgroupBarrier();
+ atomicStore(&results.value[id_0].r0, r0);
+`;
+
+const workgroupStorageMemoryCorw1TestCode = `
+ let r0 = atomicLoad(&test_locations.value[x_0]);
+ atomicStore(&test_locations.value[y_0], 1u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+const workgroupMemoryCorw1TestCode = `
+ let r0 = atomicLoad(&wg_test_locations[x_0]);
+ atomicStore(&wg_test_locations[y_0], 1u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+g.test('corw1').
+desc(
+ `One thread first reads from a memory location x and then writes 1 to x. If the read observes the subsequent
+ write, there has been a coherence violation.
+ `
+).
+paramsSimple([
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCorw1TestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCorw1TestCode
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCorw1TestCode
+}]
+).
+fn(async (t) => {
+ const resultCode = `
+ if (r0 == 0u) {
+ atomicAdd(&test_results.seq, 1u);
+ } else if (r0 == 1u) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(t.params._testCode, t.params.memType, t.params.testType);
+ const resultShader = buildResultShader(resultCode, t.params.testType, ResultType.TwoBehavior);
+ const params = {
+ ...memoryModelTestParams,
+ numBehaviors: 2
+ };
+ const memModelTester = new MemoryModelTester(t, params, testShader, resultShader);
+ await memModelTester.run(60, 1);
+});
+
+const storageMemoryCorw2TestCode = `
+ let r0 = atomicLoad(&test_locations.value[x_0]);
+ atomicStore(&test_locations.value[y_0], 1u);
+ atomicStore(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[id_0].r0, r0);
+`;
+
+const workgroupStorageMemoryCorw2TestCode = `
+ let r0 = atomicLoad(&test_locations.value[x_0]);
+ atomicStore(&test_locations.value[y_0], 1u);
+ atomicStore(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+const storageMemoryCorw2RMWTestCode = `
+ let r0 = atomicLoad(&test_locations.value[x_0]);
+ atomicStore(&test_locations.value[y_0], 1u);
+ atomicExchange(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[id_0].r0, r0);
+`;
+
+const workgroupStorageMemoryCorw2RMWTestCode = `
+ let r0 = atomicLoad(&test_locations.value[x_0]);
+ atomicStore(&test_locations.value[y_0], 1u);
+ atomicExchange(&test_locations.value[x_1], 2u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
+const workgroupMemoryCorw2TestCode = `
+ let r0 = atomicLoad(&wg_test_locations[x_0]);
+ atomicStore(&wg_test_locations[y_0], 1u);
+ atomicStore(&wg_test_locations[x_1], 2u);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+`;
+
+const workgroupMemoryCorw2RMWTestCode = `
+ let r0 = atomicLoad(&wg_test_locations[x_0]);
+ atomicStore(&wg_test_locations[y_0], 1u);
+ atomicExchange(&wg_test_locations[x_1], 2u);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+`;
+
+g.test('corw2').
+desc(
+ `The first thread reads from some memory location x, and then writes 1 to x. The second thread
+ writes 2 to x. If the first thread reads the value 2, but the value in memory after the test
+ completes is 1, then the instructions on the first thread have been re-ordered, leading to a
+ coherence violation.
+ `
+).
+paramsSimple([
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCorw2TestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.InterWorkgroup,
+ _testCode: storageMemoryCorw2RMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCorw2TestCode
+},
+{
+ memType: MemoryType.AtomicStorageClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupStorageMemoryCorw2RMWTestCode,
+ extraFlags: 'rmw_variant'
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCorw2TestCode
+},
+{
+ memType: MemoryType.AtomicWorkgroupClass,
+ testType: TestType.IntraWorkgroup,
+ _testCode: workgroupMemoryCorw2RMWTestCode,
+ extraFlags: 'rmw_variant'
+}]
+).
+fn(async (t) => {
+ const resultCode = `
+ if ((r0 == 0u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 2u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 0u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 2u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `;
+ const testShader = buildTestShader(t.params._testCode, t.params.memType, t.params.testType);
+ const resultShader = buildResultShader(resultCode, t.params.testType, ResultType.FourBehavior);
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ resultShader
+ );
+ await memModelTester.run(60, 3);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/memory_model_setup.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/memory_model_setup.js
new file mode 100644
index 0000000000..8d107ae597
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/memory_model_setup.js
@@ -0,0 +1,1118 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { checkElementsPassPredicate } from '../../../util/check_contents.js';
+/* All buffer sizes are counted in units of 4-byte words. */
+
+/**
+ * The value type loaded and stored from memory.
+ * This is what the WGSL spec calls 'store type' for the locations being accessed.
+ * The GPU buffers are sized assuming this type is at most 4 bytes.
+ *
+ * 'u32' is the default case; it can be atomically loaded and stored.
+ * 'f16' is interesting because it is not 32-bits, and can't be the store type
+ * for atomic accesses.
+ */
+
+export const kAccessValueTypes = ['f16', 'u32'];
+
+/* Parameter values are set heuristically, typically by a time-intensive search. */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** The number of memory locations accessed by a test. Currently, only tests with up to 2 memory locations are supported. */
+const numMemLocations = 2;
+
+/** The number of read outputs per test that need to be analyzed in the result aggregation shader. Currently, only tests with up to 2 read outputs are supported. */
+const numReadOutputs = 2;
+
+/** Represents a device buffer and a utility buffer for resetting memory and copying parameters. */
+
+
+
+
+
+
+
+
+
+/** Specifies the buffers used during a memory model test. */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** The number of stress params to add to the stress params buffer. */
+const numStressParams = 12;
+const barrierParamIndex = 0;
+const memStressIndex = 1;
+const memStressIterationsIndex = 2;
+const memStressPatternIndex = 3;
+const preStressIndex = 4;
+const preStressIterationsIndex = 5;
+const preStressPatternIndex = 6;
+const permuteFirstIndex = 7;
+const permuteSecondIndex = 8;
+const testingWorkgroupsIndex = 9;
+const memStrideIndex = 10;
+const memLocationOffsetIndex = 11;
+
+/**
+ * All memory used in these consists of a four byte word, so this value is used to correctly set the byte size of buffers that
+ * are read to/written from during tests and for storing test results.
+ */
+const bytesPerWord = 4;
+
+/**
+ * Returns the shader preamble based on the access value type:
+ * - enable directives, if necessary
+ * - the type alias for AccessValueType
+ */
+function shaderPreamble(accessValueType) {
+ if (accessValueType === 'f16') {
+ return 'enable f16;\nalias AccessValueTy = f16;\n';
+ }
+ return `alias AccessValueTy = ${accessValueType};\n`;
+}
+
+/**
+ * Implements setup code necessary to run a memory model test. A test consists of two parts:
+ * 1.) A test shader that runs a specified memory model litmus test and attempts to reveal a weak (disallowed) behavior.
+ * At a high level, a test shader consists of a set of testing workgroups where every invocation executes the litmus test
+ * on a set of test locations, and a set of stressing workgroups where every invocation accesses a specified memory location
+ * in a random pattern.
+ *
+ * The main buffer variables are:
+ *
+ * `test_locations`: invocations access entries in this array, trying to
+ * evoke weak behaviours.
+ *
+ * This is array<AccessValueTy> or array<atomic<u32>>.
+ * AccessValueTy is either f16 or u32.
+ * Note that atomic<u32> is only used when AccessValueTy is u32.
+ *
+ * `results`: holds the observed values, which is where we can see
+ * whether a weak behaviour was observed.
+ *
+ * This is an array<atomic<u32>>.
+ *
+ * The others are used to parameterize and stress the main activity.
+ *
+ * 2.) A result shader that takes the output of the test shader, which consists of the memory locations accessed during the test
+ * and the results of any reads made during the test, and aggregate the results based on the possible behaviors of the test.
+ *
+ * The first two buffer variables are the same buffers as for the test shader:
+ *
+ * `test_locations` is the same as `test_locations` from the test shader,
+ * but is mapped as array<AccessValueTy>.
+ *
+ * `read_results` is the same buffer as `results` from the test shader.
+ *
+ * The other variables are used to accumulate a summary that counts the weak behaviours stimulated and recorded by the
+ * test shader.
+ */
+export class MemoryModelTester {
+
+
+
+
+
+
+
+
+ /** Sets up a memory model test by initializing buffers and pipeline layouts. */
+ constructor(
+ t,
+ params,
+ testShader,
+ resultShader,
+ accessValueType = 'u32')
+ {
+ this.test = t;
+ this.params = params;
+
+ testShader = shaderPreamble(accessValueType) + testShader;
+ resultShader = shaderPreamble(accessValueType) + resultShader;
+
+ // set up buffers
+ const testingThreads = this.params.workgroupSize * this.params.testingWorkgroups;
+ const testLocationsSize =
+ testingThreads * numMemLocations * this.params.memStride * bytesPerWord;
+ const testLocationsBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: testLocationsSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: testLocationsSize,
+ usage: GPUBufferUsage.COPY_SRC
+ }),
+ size: testLocationsSize
+ };
+
+ const readResultsSize = testingThreads * numReadOutputs * bytesPerWord;
+ const readResultsBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: readResultsSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: readResultsSize,
+ usage: GPUBufferUsage.COPY_SRC
+ }),
+ size: readResultsSize
+ };
+
+ const testResultsSize = this.params.numBehaviors * bytesPerWord;
+ const testResultsBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: testResultsSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: testResultsSize,
+ usage: GPUBufferUsage.COPY_SRC
+ }),
+ size: testResultsSize
+ };
+
+ const shuffledWorkgroupsSize = this.params.maxWorkgroups * bytesPerWord;
+ const shuffledWorkgroupsBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: shuffledWorkgroupsSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: shuffledWorkgroupsSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
+ }),
+ size: shuffledWorkgroupsSize
+ };
+
+ const barrierSize = bytesPerWord;
+ const barrierBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: barrierSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: barrierSize,
+ usage: GPUBufferUsage.COPY_SRC
+ }),
+ size: barrierSize
+ };
+
+ const scratchpadSize = this.params.scratchMemorySize * bytesPerWord;
+ const scratchpadBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: scratchpadSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: scratchpadSize,
+ usage: GPUBufferUsage.COPY_SRC
+ }),
+ size: scratchpadSize
+ };
+
+ const scratchMemoryLocationsSize = this.params.maxWorkgroups * bytesPerWord;
+ const scratchMemoryLocationsBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: scratchMemoryLocationsSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: scratchMemoryLocationsSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
+ }),
+ size: scratchMemoryLocationsSize
+ };
+
+ const stressParamsSize = numStressParams * bytesPerWord;
+ const stressParamsBuffer = {
+ deviceBuf: this.test.device.createBuffer({
+ size: stressParamsSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM
+ }),
+ srcBuf: this.test.device.createBuffer({
+ size: stressParamsSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
+ }),
+ size: stressParamsSize
+ };
+
+ this.buffers = {
+ testLocations: testLocationsBuffer,
+ readResults: readResultsBuffer,
+ testResults: testResultsBuffer,
+ shuffledWorkgroups: shuffledWorkgroupsBuffer,
+ barrier: barrierBuffer,
+ scratchpad: scratchpadBuffer,
+ scratchMemoryLocations: scratchMemoryLocationsBuffer,
+ stressParams: stressParamsBuffer
+ };
+
+ // set up pipeline layouts
+ const testLayout = this.test.device.createBindGroupLayout({
+ 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' } }]
+
+ });
+ this.testPipeline = this.test.device.createComputePipeline({
+ layout: this.test.device.createPipelineLayout({
+ bindGroupLayouts: [testLayout]
+ }),
+ compute: {
+ module: this.test.device.createShaderModule({
+ code: testShader
+ }),
+ entryPoint: 'main'
+ }
+ });
+ this.testBindGroup = this.test.device.createBindGroup({
+ 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 } }],
+
+ layout: testLayout
+ });
+
+ const resultLayout = this.test.device.createBindGroupLayout({
+ entries: [
+ { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
+ { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
+ { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
+ { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } }]
+
+ });
+ this.resultPipeline = this.test.device.createComputePipeline({
+ layout: this.test.device.createPipelineLayout({
+ bindGroupLayouts: [resultLayout]
+ }),
+ compute: {
+ module: this.test.device.createShaderModule({
+ code: resultShader
+ }),
+ entryPoint: 'main'
+ }
+ });
+ this.resultBindGroup = this.test.device.createBindGroup({
+ entries: [
+ { binding: 0, resource: { buffer: this.buffers.testLocations.deviceBuf } },
+ { binding: 1, resource: { buffer: this.buffers.readResults.deviceBuf } },
+ { binding: 2, resource: { buffer: this.buffers.testResults.deviceBuf } },
+ { binding: 3, resource: { buffer: this.buffers.stressParams.deviceBuf } }],
+
+ layout: resultLayout
+ });
+ }
+
+ /**
+ * Run the test for the specified number of iterations. Checks the testResults buffer on the weakIndex; if
+ * this value is not 0 then the test has failed. The number of iterations is chosen per test so that the
+ * full set of tests meets some time budget while still being reasonably effective at uncovering issues.
+ * Currently, we aim for each test to complete in under one second.
+ */
+ async run(iterations, weakIndex) {
+ for (let i = 0; i < iterations; i++) {
+ const numWorkgroups = this.getRandomInRange(
+ this.params.testingWorkgroups,
+ this.params.maxWorkgroups
+ );
+ await this.setShuffledWorkgroups(numWorkgroups);
+ await this.setScratchLocations(numWorkgroups);
+ await this.setStressParams();
+ const encoder = this.test.device.createCommandEncoder();
+ this.copyBufferToBuffer(encoder, this.buffers.testLocations);
+ this.copyBufferToBuffer(encoder, this.buffers.readResults);
+ this.copyBufferToBuffer(encoder, this.buffers.testResults);
+ this.copyBufferToBuffer(encoder, this.buffers.barrier);
+ this.copyBufferToBuffer(encoder, this.buffers.shuffledWorkgroups);
+ this.copyBufferToBuffer(encoder, this.buffers.scratchpad);
+ this.copyBufferToBuffer(encoder, this.buffers.scratchMemoryLocations);
+ this.copyBufferToBuffer(encoder, this.buffers.stressParams);
+
+ const testPass = encoder.beginComputePass();
+ testPass.setPipeline(this.testPipeline);
+ testPass.setBindGroup(0, this.testBindGroup);
+ testPass.dispatchWorkgroups(numWorkgroups);
+ testPass.end();
+
+ const resultPass = encoder.beginComputePass();
+ resultPass.setPipeline(this.resultPipeline);
+ resultPass.setBindGroup(0, this.resultBindGroup);
+ resultPass.dispatchWorkgroups(this.params.testingWorkgroups);
+ resultPass.end();
+
+ this.test.device.queue.submit([encoder.finish()]);
+ this.test.expectGPUBufferValuesPassCheck(
+ this.buffers.testResults.deviceBuf,
+ this.checkWeakIndex(weakIndex),
+ {
+ type: Uint32Array,
+ typedLength: this.params.numBehaviors
+ }
+ );
+ }
+ }
+
+ /** Returns a function that checks whether the test passes, given a weak index and the test results buffer. */
+ checkWeakIndex(weakIndex) {
+ const checkResult = this.checkResult(weakIndex);
+ const resultPrinter = this.resultPrinter(weakIndex);
+ return function (a) {
+ return checkElementsPassPredicate(a, checkResult, {
+ predicatePrinter: [{ leftHeader: 'expected ==', getValueForCell: resultPrinter }]
+ });
+ };
+ }
+
+ /**
+ * Returns a function that checks whether the specified weak index's value is not equal to 0.
+ * 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.
+ */
+ checkResult(weakIndex) {
+ return function (i, v) {
+ if (i === weakIndex && v > 0) {
+ return false;
+ }
+ return true;
+ };
+ }
+
+ /** Returns a printer function that visualizes the results of checking the test results. */
+ resultPrinter(weakIndex) {
+ return function (i) {
+ if (i === weakIndex) {
+ return 0;
+ } else {
+ return 'any value';
+ }
+ };
+ }
+
+ /** Utility method that simplifies copying source buffers to device buffers. */
+ copyBufferToBuffer(encoder, buffer) {
+ encoder.copyBufferToBuffer(buffer.srcBuf, 0, buffer.deviceBuf, 0, buffer.size);
+ }
+
+ /** Returns a random integer between 0 and the max. */
+ getRandomInt(max) {
+ return Math.floor(Math.random() * max);
+ }
+
+ /** Returns a random number in between the min and max values. */
+ getRandomInRange(min, max) {
+ if (min === max) {
+ return min;
+ } else {
+ const offset = this.getRandomInt(max - min);
+ return min + offset;
+ }
+ }
+
+ /** Returns a permuted array using a simple Fisher-Yates shuffle algorithm. */
+ shuffleArray(a) {
+ for (let i = a.length - 1; i >= 0; i--) {
+ const toSwap = this.getRandomInt(i + 1);
+ const temp = a[toSwap];
+ a[toSwap] = a[i];
+ a[i] = temp;
+ }
+ }
+
+ /**
+ * Shuffles the order of workgroup ids, so that threads operating on the same memory location are not always in
+ * consecutive workgroups.
+ */
+ async setShuffledWorkgroups(numWorkgroups) {
+ await this.buffers.shuffledWorkgroups.srcBuf.mapAsync(GPUMapMode.WRITE);
+ const shuffledWorkgroupsBuffer = this.buffers.shuffledWorkgroups.srcBuf.getMappedRange();
+ const shuffledWorkgroupsArray = new Uint32Array(shuffledWorkgroupsBuffer);
+ for (let i = 0; i < numWorkgroups; i++) {
+ shuffledWorkgroupsArray[i] = i;
+ }
+ if (this.getRandomInt(100) < this.params.shufflePct) {
+ for (let i = numWorkgroups - 1; i > 0; i--) {
+ const x = this.getRandomInt(i + 1);
+ const temp = shuffledWorkgroupsArray[i];
+ shuffledWorkgroupsArray[i] = shuffledWorkgroupsArray[x];
+ shuffledWorkgroupsArray[x] = temp;
+ }
+ }
+ this.buffers.shuffledWorkgroups.srcBuf.unmap();
+ }
+
+ /** Sets the memory locations that stressing workgroups will access. Uses either a chunking or round robin assignment strategy. */
+ async setScratchLocations(numWorkgroups) {
+ await this.buffers.scratchMemoryLocations.srcBuf.mapAsync(GPUMapMode.WRITE);
+ const scratchLocationsArrayBuffer = this.buffers.scratchMemoryLocations.srcBuf.getMappedRange();
+ const scratchLocationsArray = new Uint32Array(scratchLocationsArrayBuffer);
+ const scratchNumRegions = this.params.scratchMemorySize / this.params.stressLineSize;
+ const scratchRegions = [...Array(scratchNumRegions).keys()];
+ this.shuffleArray(scratchRegions);
+ for (let i = 0; i < this.params.stressTargetLines; i++) {
+ const region = scratchRegions[i];
+ const locInRegion = this.getRandomInt(this.params.stressLineSize);
+ if (this.getRandomInt(100) < this.params.stressStrategyBalancePct) {
+ // In the round-robin case, the current scratch location is striped across all workgroups.
+ for (let j = i; j < numWorkgroups; j += this.params.stressTargetLines) {
+ scratchLocationsArray[j] = region * this.params.stressLineSize + locInRegion;
+ }
+ } else {
+ // In the chunking case, the current scratch location is assigned to a block of workgroups. The final scratch
+ // location may be assigned to more workgroups, if the number of scratch locations does not cleanly divide the
+ // number of workgroups.
+ const workgroupsPerLocation = numWorkgroups / this.params.stressTargetLines;
+ for (let j = 0; j < workgroupsPerLocation; j++) {
+ scratchLocationsArray[i * workgroupsPerLocation + j] =
+ region * this.params.stressLineSize + locInRegion;
+ }
+ if (
+ i === this.params.stressTargetLines - 1 &&
+ numWorkgroups % this.params.stressTargetLines !== 0)
+ {
+ for (let j = 0; j < numWorkgroups % this.params.stressTargetLines; j++) {
+ scratchLocationsArray[numWorkgroups - j - 1] =
+ region * this.params.stressLineSize + locInRegion;
+ }
+ }
+ }
+ }
+ this.buffers.scratchMemoryLocations.srcBuf.unmap();
+ }
+
+ /** Sets the parameters that are used by the shader to calculate memory locations and perform stress. */
+ async setStressParams() {
+ await this.buffers.stressParams.srcBuf.mapAsync(GPUMapMode.WRITE);
+ const stressParamsArrayBuffer = this.buffers.stressParams.srcBuf.getMappedRange();
+ const stressParamsArray = new Uint32Array(stressParamsArrayBuffer);
+ if (this.getRandomInt(100) < this.params.barrierPct) {
+ stressParamsArray[barrierParamIndex] = 1;
+ } else {
+ stressParamsArray[barrierParamIndex] = 0;
+ }
+ if (this.getRandomInt(100) < this.params.memStressPct) {
+ stressParamsArray[memStressIndex] = 1;
+ } else {
+ stressParamsArray[memStressIndex] = 0;
+ }
+ stressParamsArray[memStressIterationsIndex] = this.params.memStressIterations;
+ const memStressStoreFirst = this.getRandomInt(100) < this.params.memStressStoreFirstPct;
+ const memStressStoreSecond = this.getRandomInt(100) < this.params.memStressStoreSecondPct;
+ let memStressPattern;
+ if (memStressStoreFirst && memStressStoreSecond) {
+ memStressPattern = 0;
+ } else if (memStressStoreFirst && !memStressStoreSecond) {
+ memStressPattern = 1;
+ } else if (!memStressStoreFirst && memStressStoreSecond) {
+ memStressPattern = 2;
+ } else {
+ memStressPattern = 3;
+ }
+ stressParamsArray[memStressPatternIndex] = memStressPattern;
+ if (this.getRandomInt(100) < this.params.preStressPct) {
+ stressParamsArray[preStressIndex] = 1;
+ } else {
+ stressParamsArray[preStressIndex] = 0;
+ }
+ stressParamsArray[preStressIterationsIndex] = this.params.preStressIterations;
+ const preStressStoreFirst = this.getRandomInt(100) < this.params.preStressStoreFirstPct;
+ const preStressStoreSecond = this.getRandomInt(100) < this.params.preStressStoreSecondPct;
+ let preStressPattern;
+ if (preStressStoreFirst && preStressStoreSecond) {
+ preStressPattern = 0;
+ } else if (preStressStoreFirst && !preStressStoreSecond) {
+ preStressPattern = 1;
+ } else if (!preStressStoreFirst && preStressStoreSecond) {
+ preStressPattern = 2;
+ } else {
+ preStressPattern = 3;
+ }
+ stressParamsArray[preStressPatternIndex] = preStressPattern;
+ stressParamsArray[permuteFirstIndex] = this.params.permuteFirst;
+ stressParamsArray[permuteSecondIndex] = this.params.permuteSecond;
+ stressParamsArray[testingWorkgroupsIndex] = this.params.testingWorkgroups;
+ stressParamsArray[memStrideIndex] = this.params.memStride;
+ if (this.params.aliasedMemory) {
+ stressParamsArray[memLocationOffsetIndex] = 0;
+ } else {
+ stressParamsArray[memLocationOffsetIndex] = this.params.memStride;
+ }
+ this.buffers.stressParams.srcBuf.unmap();
+ }
+}
+
+/** Defines common data structures used in memory model test shaders. */
+const shaderMemStructures = `
+ struct Memory {
+ value: array<AccessValueTy>
+ };
+
+ struct AtomicMemory {
+ value: array<atomic<u32>>
+ };
+
+ struct IndexMemory {
+ value: array<u32>
+ };
+
+ struct ReadResult {
+ r0: atomic<u32>,
+ r1: atomic<u32>,
+ };
+
+ struct ReadResults {
+ value: array<ReadResult>
+ };
+
+ struct StressParamsMemory {
+ do_barrier: u32,
+ mem_stress: u32,
+ mem_stress_iterations: u32,
+ mem_stress_pattern: u32,
+ pre_stress: u32,
+ pre_stress_iterations: u32,
+ pre_stress_pattern: u32,
+ permute_first: u32,
+ permute_second: u32,
+ testing_workgroups: u32,
+ mem_stride: u32,
+ location_offset: u32,
+ };
+`;
+
+/**
+ * Structure to hold the counts of occurrences of the possible behaviors of a two-thread, four-instruction test.
+ * "seq0" means the first invocation's instructions are observed to have occurred before the second invocation's instructions.
+ * "seq1" means the second invocation's instructions are observed to have occurred before the first invocation's instructions.
+ * "interleaved" means there was an observation of some interleaving of instructions between the two invocations.
+ * "weak" means there was an observation of some ordering of instructions that is inconsistent with the WebGPU memory model.
+ */
+const fourBehaviorTestResultStructure = `
+ struct TestResults {
+ seq0: atomic<u32>,
+ seq1: atomic<u32>,
+ interleaved: atomic<u32>,
+ weak: atomic<u32>,
+ };
+`;
+
+/**
+ * Defines the possible behaviors of a two instruction test. Used to test the behavior of non-atomic memory with barriers and
+ * one-thread coherence tests.
+ * "seq" means that the expected, sequential behavior occurred.
+ * "weak" means that an unexpected, inconsistent behavior occurred.
+ */
+const twoBehaviorTestResultStructure = `
+ struct TestResults {
+ seq: atomic<u32>,
+ weak: atomic<u32>,
+ };
+`;
+
+/** Common bindings used in the test shader phase of a test. */
+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;
+`;
+
+/** The combined bindings for a test on atomic memory. */
+const atomicTestShaderBindings = [
+`
+ @group(0) @binding(0) var<storage, read_write> test_locations : AtomicMemory;
+`,
+commonTestShaderBindings].
+join('\n');
+
+/** The combined bindings for a test on non-atomic memory. */
+const nonAtomicTestShaderBindings = [
+`
+ @group(0) @binding(0) var<storage, read_write> test_locations : Memory;
+`,
+commonTestShaderBindings].
+join('\n');
+
+/** Bindings used in the result aggregation phase of the test. */
+const resultShaderBindings = `
+ @group(0) @binding(0) var<storage, read_write> test_locations : Memory;
+ @group(0) @binding(1) var<storage, read_write> read_results : ReadResults;
+ @group(0) @binding(2) var<storage, read_write> test_results : TestResults;
+ @group(0) @binding(3) var<uniform> stress_params : StressParamsMemory;
+`;
+
+/**
+ * For tests that operate on workgroup memory, include this definition. 3584 memory locations is
+ * large enough to accommodate the maximum memory size needed per workgroup for testing, which is
+ * 256 invocations per workgroup x 2 memory locations x 7 (memStride, or max stride between successive memory locations).
+ * Should change to a pipeline overridable constant when possible.
+ */
+const atomicWorkgroupMemory = `
+ var<workgroup> wg_test_locations: array<atomic<u32>, 3584>;
+`;
+
+/**
+ * For tests that operate on non-atomic workgroup memory, include this definition. 3584 memory locations
+ * is large enough to accommodate the maximum memory size needed per workgroup for testing.
+ */
+const nonAtomicWorkgroupMemory = `
+ var<workgroup> wg_test_locations: array<AccessValueTy, 3584>;
+`;
+
+/**
+ * Functions used to calculate memory locations for each invocation, for both testing and result aggregation.
+ * The permute function ensures a random permutation based on multiplying and modding by coprime numbers. The stripe
+ * workgroup function ensures that invocations coordinating on a test are spread out across different workgroups.
+ */
+const memoryLocationFunctions = `
+ fn permute_id(id: u32, factor: u32, mask: u32) -> u32 {
+ return (id * factor) % mask;
+ }
+
+ fn stripe_workgroup(workgroup_id: u32, local_id: u32) -> u32 {
+ return (workgroup_id + 1u + local_id % (stress_params.testing_workgroups - 1u)) % stress_params.testing_workgroups;
+ }
+`;
+
+/** 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
+ // effects of a control barrier. The barrier spins until either all invocations have incremented the atomic
+ // variable or 1024 loops have occurred. 1024 was chosen because it gives more time for invocations to enter
+ // 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);
+ loop {
+ if (i == 1024u || bar_val >= limit) {
+ break;
+ }
+ bar_val = atomicAdd(&barrier.value[0], 0u);
+ i = i + 1u;
+ }
+ }
+
+ // Perform iterations of stress, depending on the specified pattern. Pattern 0 is store-store, pattern 1 is store-load,
+ // pattern 2 is load-store, and pattern 3 is load-load. The extra if condition (if tmpX > 100000u), is used to avoid
+ // 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];
+ switch(pattern) {
+ case 0u: {
+ for(var i: u32 = 0u; i < iterations; i = i + 1u) {
+ scratchpad.value[addr] = i;
+ 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];
+ if (tmp1 > 100000u) {
+ scratchpad.value[addr] = i;
+ break;
+ }
+ }
+ }
+ case 2u: {
+ for(var i: u32 = 0u; i < iterations; i = i + 1u) {
+ let tmp1: u32 = scratchpad.value[addr];
+ if (tmp1 > 100000u) {
+ scratchpad.value[addr] = i;
+ break;
+ }
+ scratchpad.value[addr] = i;
+ }
+ }
+ case 3u: {
+ for(var i: u32 = 0u; i < iterations; i = i + 1u) {
+ let tmp1: u32 = scratchpad.value[addr];
+ if (tmp1 > 100000u) {
+ scratchpad.value[addr] = i;
+ break;
+ }
+ let tmp2: u32 = scratchpad.value[addr];
+ if (tmp2 > 100000u) {
+ scratchpad.value[addr] = i;
+ break;
+ }
+ }
+ }
+ default: {
+ }
+ }
+ }
+`;
+
+/**
+ * Entry point to both test and result shaders. One-dimensional workgroup size is hardcoded to 256, until
+ * pipeline overridable constants are supported.
+ */
+const shaderEntryPoint = `
+ // Change to pipeline overridable constant when possible.
+ const workgroupXSize = 256u;
+ @compute @workgroup_size(workgroupXSize) fn main(
+ @builtin(local_invocation_id) local_invocation_id : vec3<u32>,
+ @builtin(workgroup_id) workgroup_id : vec3<u32>) {
+`;
+
+/** All test shaders first calculate the shuffled workgroup. */
+const testShaderCommonHeader = `
+ let shuffled_workgroup = shuffled_workgroups.value[workgroup_id[0]];
+ if (shuffled_workgroup < stress_params.testing_workgroups) {
+`;
+
+/**
+ * All test shaders must calculate addresses for memory locations used in the test. Not all these addresses are
+ * used in every test, but no test uses more than these addresses.
+ */
+const testShaderCommonCalculations = `
+ let x_0 = id_0 * stress_params.mem_stride * 2u;
+ let y_0 = permute_id(id_0, stress_params.permute_second, total_ids) * stress_params.mem_stride * 2u + stress_params.location_offset;
+ let x_1 = id_1 * stress_params.mem_stride * 2u;
+ let y_1 = permute_id(id_1, stress_params.permute_second, total_ids) * stress_params.mem_stride * 2u + stress_params.location_offset;
+ if (stress_params.pre_stress == 1u) {
+ do_stress(stress_params.pre_stress_iterations, stress_params.pre_stress_pattern, shuffled_workgroup);
+ }
+`;
+
+/**
+ * An inter-workgroup test calculates two sets of memory locations that are guaranteed to be in separate workgroups.
+ * If the bounded spin-loop barrier is called, it attempts to wait for all invocations in all workgroups.
+ */
+const interWorkgroupTestShaderCode = [
+`
+ let total_ids = workgroupXSize * stress_params.testing_workgroups;
+ let id_0 = shuffled_workgroup * workgroupXSize + local_invocation_id[0];
+ let new_workgroup = stripe_workgroup(shuffled_workgroup, local_invocation_id[0]);
+ let id_1 = new_workgroup * workgroupXSize + permute_id(local_invocation_id[0], stress_params.permute_first, workgroupXSize);
+`,
+testShaderCommonCalculations,
+`
+ if (stress_params.do_barrier == 1u) {
+ spin(workgroupXSize * stress_params.testing_workgroups);
+ }
+`].
+join('\n');
+
+/**
+ * An intra-workgroup test calculates two set of memory locations that are guaranteed to be in the same workgroup.
+ * If the bounded spin-loop barrier is called, it attempts to wait for all invocations in the same workgroup.
+ */
+const intraWorkgroupTestShaderCode = [
+`
+ let total_ids = workgroupXSize;
+ let id_0 = local_invocation_id[0];
+ let id_1 = permute_id(local_invocation_id[0], stress_params.permute_first, workgroupXSize);
+`,
+testShaderCommonCalculations,
+`
+ if (stress_params.do_barrier == 1u) {
+ spin(workgroupXSize);
+ }
+`].
+join('\n');
+
+/**
+ * Tests that operate on storage memory and communicate with invocations in the same workgroup must offset their locations
+ * relative to global memory.
+ */
+const storageIntraWorkgroupTestShaderCode = `
+ let total_ids = workgroupXSize;
+ let id_0 = local_invocation_id[0];
+ let id_1 = permute_id(local_invocation_id[0], stress_params.permute_first, workgroupXSize);
+ let x_0 = (shuffled_workgroup * workgroupXSize + id_0) * stress_params.mem_stride * 2u;
+ let y_0 = (shuffled_workgroup * workgroupXSize + permute_id(id_0, stress_params.permute_second, total_ids)) * stress_params.mem_stride * 2u + stress_params.location_offset;
+ let x_1 = (shuffled_workgroup * workgroupXSize + id_1) * stress_params.mem_stride * 2u;
+ let y_1 = (shuffled_workgroup * workgroupXSize + permute_id(id_1, stress_params.permute_second, total_ids)) * stress_params.mem_stride * 2u + stress_params.location_offset;
+ if (stress_params.pre_stress == 1u) {
+ do_stress(stress_params.pre_stress_iterations, stress_params.pre_stress_pattern, shuffled_workgroup);
+ }
+ if (stress_params.do_barrier == 1u) {
+ spin(workgroupXSize);
+ }
+`;
+
+/** All test shaders may perform stress with non-testing threads. */
+const testShaderCommonFooter = `
+ } else if (stress_params.mem_stress == 1u) {
+ do_stress(stress_params.mem_stress_iterations, stress_params.mem_stress_pattern, shuffled_workgroup);
+ }
+ }
+`;
+
+/**
+ * All result shaders must calculate memory locations used in the test. Not all these locations are
+ * used in every result shader, but no result shader uses more than these locations.
+ *
+ * Each value read from test_locations is converted from AccessValueTy to u32
+ * before storing it in the read result. This assumes u32(AccessValueTy)
+ * is either an identity function u32(u32) or a value-converting overload such
+ * as u32(f16).
+ */
+const resultShaderCommonCalculations = `
+ let id_0 = workgroup_id[0] * workgroupXSize + local_invocation_id[0];
+ let x_0 = id_0 * stress_params.mem_stride * 2u;
+ let mem_x_0 = u32(test_locations.value[x_0]);
+ let r0 = atomicLoad(&read_results.value[id_0].r0);
+ let r1 = atomicLoad(&read_results.value[id_0].r1);
+`;
+
+/** Common result shader code for an inter-workgroup test. */
+const interWorkgroupResultShaderCode = [
+resultShaderCommonCalculations,
+`
+ let total_ids = workgroupXSize * stress_params.testing_workgroups;
+ let y_0 = permute_id(id_0, stress_params.permute_second, total_ids) * stress_params.mem_stride * 2u + stress_params.location_offset;
+ let mem_y_0 = u32(test_locations.value[y_0]);
+`].
+join('\n');
+
+/** Common result shader code for an intra-workgroup test. */
+const intraWorkgroupResultShaderCode = [
+resultShaderCommonCalculations,
+`
+ let total_ids = workgroupXSize;
+ let y_0 = (workgroup_id[0] * workgroupXSize + permute_id(local_invocation_id[0], stress_params.permute_second, total_ids)) * stress_params.mem_stride * 2u + stress_params.location_offset;
+ let mem_y_0 = u32(test_locations.value[y_0]);
+`].
+join('\n');
+
+/** Ending bracket for result shaders. */
+const resultShaderCommonFooter = `
+}
+`;
+
+/** The common shader code for test shaders that perform atomic storage class memory litmus tests. */
+const storageMemoryAtomicTestShaderCode = [
+shaderMemStructures,
+atomicTestShaderBindings,
+memoryLocationFunctions,
+testShaderFunctions,
+shaderEntryPoint,
+testShaderCommonHeader].
+join('\n');
+
+/** The common shader code for test shaders that perform non-atomic storage class memory litmus tests. */
+const storageMemoryNonAtomicTestShaderCode = [
+shaderMemStructures,
+nonAtomicTestShaderBindings,
+memoryLocationFunctions,
+testShaderFunctions,
+shaderEntryPoint,
+testShaderCommonHeader].
+join('\n');
+
+/** The common shader code for test shaders that perform atomic workgroup class memory litmus tests. */
+const workgroupMemoryAtomicTestShaderCode = [
+shaderMemStructures,
+atomicTestShaderBindings,
+atomicWorkgroupMemory,
+memoryLocationFunctions,
+testShaderFunctions,
+shaderEntryPoint,
+testShaderCommonHeader].
+join('\n');
+
+/** The common shader code for test shaders that perform non-atomic workgroup class memory litmus tests. */
+const workgroupMemoryNonAtomicTestShaderCode = [
+shaderMemStructures,
+nonAtomicTestShaderBindings,
+nonAtomicWorkgroupMemory,
+memoryLocationFunctions,
+testShaderFunctions,
+shaderEntryPoint,
+testShaderCommonHeader].
+join('\n');
+
+/** The common shader code for all result shaders. */
+const resultShaderCommonCode = [
+shaderMemStructures,
+resultShaderBindings,
+memoryLocationFunctions,
+shaderEntryPoint].
+join('\n');
+
+/**
+ * Defines the types of possible memory a test is operating on. Used as part of the process of building shader code from
+ * its composite parts.
+ */
+export let MemoryType = /*#__PURE__*/function (MemoryType) {MemoryType["AtomicStorageClass"] = "atomic_storage";MemoryType["NonAtomicStorageClass"] = "non_atomic_storage";MemoryType["AtomicWorkgroupClass"] = "atomic_workgroup";MemoryType["NonAtomicWorkgroupClass"] = "non_atomic_workgroup";return MemoryType;}({});
+
+
+
+
+
+
+
+
+
+
+/**
+ * Defines the relative positions of two invocations coordinating on a test. Used as part of the process of building shader
+ * code from its composite parts.
+ */
+export let TestType = /*#__PURE__*/function (TestType) {TestType["InterWorkgroup"] = "inter_workgroup";TestType["IntraWorkgroup"] = "intra_workgroup";return TestType;}({});
+
+
+
+
+
+
+/** Defines the number of behaviors a test may have. */
+export let ResultType = /*#__PURE__*/function (ResultType) {ResultType[ResultType["TwoBehavior"] = 0] = "TwoBehavior";ResultType[ResultType["FourBehavior"] = 1] = "FourBehavior";return ResultType;}({});
+
+
+
+
+/**
+ * Given test code that performs the actual sequence of loads and stores, as well as a memory type and test type, returns
+ * a complete test shader.
+ */
+export function buildTestShader(
+testCode,
+memoryType,
+testType)
+{
+ let memoryTypeCode;
+ let isStorageAS = false;
+ switch (memoryType) {
+ case MemoryType.AtomicStorageClass:
+ memoryTypeCode = storageMemoryAtomicTestShaderCode;
+ isStorageAS = true;
+ break;
+ case MemoryType.NonAtomicStorageClass:
+ memoryTypeCode = storageMemoryNonAtomicTestShaderCode;
+ isStorageAS = true;
+ break;
+ case MemoryType.AtomicWorkgroupClass:
+ memoryTypeCode = workgroupMemoryAtomicTestShaderCode;
+ break;
+ case MemoryType.NonAtomicWorkgroupClass:
+ memoryTypeCode = workgroupMemoryNonAtomicTestShaderCode;
+ }
+ let testTypeCode;
+ switch (testType) {
+ case TestType.InterWorkgroup:
+ testTypeCode = interWorkgroupTestShaderCode;
+ break;
+ case TestType.IntraWorkgroup:
+ if (isStorageAS) {
+ testTypeCode = storageIntraWorkgroupTestShaderCode;
+ } else {
+ testTypeCode = intraWorkgroupTestShaderCode;
+ }
+ }
+ return [memoryTypeCode, testTypeCode, testCode, testShaderCommonFooter].join('\n');
+}
+
+/**
+ * Given result code that aggregates the possible behaviors of a test across all instances, as well as a test type and
+ * number of behaviors, returns a complete result shader.
+ */
+export function buildResultShader(
+resultCode,
+testType,
+resultType)
+{
+ let resultStructure;
+ switch (resultType) {
+ case ResultType.TwoBehavior:
+ resultStructure = twoBehaviorTestResultStructure;
+ break;
+ case ResultType.FourBehavior:
+ resultStructure = fourBehaviorTestResultStructure;
+ }
+ let testTypeCode;
+ switch (testType) {
+ case TestType.InterWorkgroup:
+ testTypeCode = interWorkgroupResultShaderCode;
+ break;
+ case TestType.IntraWorkgroup:
+ testTypeCode = intraWorkgroupResultShaderCode;
+ }
+ return [
+ resultStructure,
+ resultShaderCommonCode,
+ testTypeCode,
+ resultCode,
+ resultShaderCommonFooter].
+ join('\n');
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/weak.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/weak.spec.js
new file mode 100644
index 0000000000..57458afd7a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/memory_model/weak.spec.js
@@ -0,0 +1,429 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for properties of the WebGPU memory model involving two memory locations.
+Specifically, the acquire/release ordering provided by WebGPU's barriers can be used to disallow
+weak behaviors in several classic memory model litmus tests.`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+import {
+
+ MemoryModelTester,
+ buildTestShader,
+ MemoryType,
+ TestType,
+ buildResultShader,
+ ResultType } from
+'./memory_model_setup.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// A reasonable parameter set, determined heuristically.
+const memoryModelTestParams = {
+ workgroupSize: 256,
+ testingWorkgroups: 739,
+ maxWorkgroups: 885,
+ shufflePct: 0,
+ barrierPct: 0,
+ memStressPct: 0,
+ memStressIterations: 1024,
+ memStressStoreFirstPct: 50,
+ memStressStoreSecondPct: 50,
+ preStressPct: 100,
+ preStressIterations: 33,
+ preStressStoreFirstPct: 0,
+ preStressStoreSecondPct: 100,
+ scratchMemorySize: 1408,
+ stressLineSize: 4,
+ stressTargetLines: 11,
+ stressStrategyBalancePct: 0,
+ permuteFirst: 109,
+ permuteSecond: 419,
+ memStride: 2,
+ aliasedMemory: false,
+ numBehaviors: 4
+};
+
+const workgroupMemoryMessagePassingTestCode = `
+ atomicStore(&wg_test_locations[x_0], 1u);
+ workgroupBarrier();
+ atomicStore(&wg_test_locations[y_0], 1u);
+ let r0 = atomicLoad(&wg_test_locations[y_1]);
+ workgroupBarrier();
+ let r1 = atomicLoad(&wg_test_locations[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+const storageMemoryMessagePassingTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ storageBarrier();
+ atomicStore(&test_locations.value[y_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[y_1]);
+ storageBarrier();
+ let r1 = atomicLoad(&test_locations.value[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * u32(workgroupXSize) + id_1].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * u32(workgroupXSize) + id_1].r1, r1);
+`;
+
+g.test('message_passing').
+desc(
+ `Checks whether two reads on one thread can observe two writes in another thread in a way
+ that is inconsistent with sequential consistency. In the message passing litmus test, one
+ thread writes the value 1 to some location x and then 1 to some location y. The second thread
+ reads y and then x. If the second thread reads y == 1 and x == 0, then sequential consistency
+ has not been respected. The acquire/release semantics of WebGPU's barrier functions should disallow
+ this behavior within a workgroup.
+ `
+).
+paramsSimple([
+{ memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryMessagePassingTestCode },
+{ memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryMessagePassingTestCode }]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(
+ t.params._testCode,
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const messagePassingResultShader = buildResultShader(
+ `
+ if ((r0 == 0u && r1 == 0u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 1u && r1 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 0u && r1 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 1u && r1 == 0u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `,
+ TestType.IntraWorkgroup,
+ ResultType.FourBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ messagePassingResultShader
+ );
+ await memModelTester.run(40, 3);
+});
+
+const workgroupMemoryStoreTestCode = `
+ atomicStore(&wg_test_locations[x_0], 2u);
+ workgroupBarrier();
+ atomicStore(&wg_test_locations[y_0], 1u);
+ let r0 = atomicLoad(&wg_test_locations[y_1]);
+ workgroupBarrier();
+ atomicStore(&wg_test_locations[x_1], 1u);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+`;
+
+const storageMemoryStoreTestCode = `
+ atomicStore(&test_locations.value[x_0], 2u);
+ storageBarrier();
+ atomicStore(&test_locations.value[y_0], 1u);
+ let r0 = atomicLoad(&test_locations.value[y_1]);
+ storageBarrier();
+ atomicStore(&test_locations.value[x_1], 1u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
+g.test('store').
+desc(
+ `In the store litmus test, one thread writes 2 to some memory location x and then 1 to some memory location
+ y. A second thread reads the value of y and then writes 1 to x. If the read on the second thread returns 1,
+ but the value of x in memory after the test ends is 2, then there has been a re-ordering which is not allowed
+ when using WebGPU's barriers.
+ `
+).
+paramsSimple([
+{ memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryStoreTestCode },
+{ memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryStoreTestCode }]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(
+ t.params._testCode,
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const messagePassingResultShader = buildResultShader(
+ `
+ if ((r0 == 1u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 0u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 0u && mem_x_0 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 1u && mem_x_0 == 2u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `,
+ TestType.IntraWorkgroup,
+ ResultType.FourBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ messagePassingResultShader
+ );
+ await memModelTester.run(40, 3);
+});
+
+const workgroupMemoryLoadBufferTestCode = `
+ let r0 = atomicLoad(&wg_test_locations[y_0]);
+ workgroupBarrier();
+ atomicStore(&wg_test_locations[x_0], 1u);
+ let r1 = atomicLoad(&wg_test_locations[x_1]);
+ workgroupBarrier();
+ atomicStore(&wg_test_locations[y_1], 1u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+const storageMemoryLoadBufferTestCode = `
+ let r0 = atomicLoad(&test_locations.value[y_0]);
+ storageBarrier();
+ atomicStore(&test_locations.value[x_0], 1u);
+ let r1 = atomicLoad(&test_locations.value[x_1]);
+ storageBarrier();
+ atomicStore(&test_locations.value[y_1], 1u);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+g.test('load_buffer').
+desc(
+ `In the load buffer litmus test, one thread reads from memory location y and then writes 1 to memory location x.
+ A second thread reads from x and then writes 1 to y. If both threads read the value 0, then the loads have been
+ buffered or re-ordered, which is not allowed when used in conjunction with WebGPU's barriers.
+ `
+).
+paramsSimple([
+{ memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryLoadBufferTestCode },
+{ memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryLoadBufferTestCode }]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(
+ t.params._testCode,
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const messagePassingResultShader = buildResultShader(
+ `
+ if ((r0 == 1u && r1 == 0u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 0u && r1 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 0u && r1 == 0u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 1u && r1 == 1u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `,
+ TestType.IntraWorkgroup,
+ ResultType.FourBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ messagePassingResultShader
+ );
+ await memModelTester.run(40, 3);
+});
+
+const workgroupMemoryReadTestCode = `
+ atomicStore(&wg_test_locations[x_0], 1u);
+ workgroupBarrier();
+ atomicExchange(&wg_test_locations[y_0], 1u);
+ atomicExchange(&wg_test_locations[y_1], 2u);
+ workgroupBarrier();
+ let r0 = atomicLoad(&wg_test_locations[x_1]);
+ workgroupBarrier();
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + y_1], atomicLoad(&wg_test_locations[y_1]));
+`;
+
+const storageMemoryReadTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ storageBarrier();
+ atomicExchange(&test_locations.value[y_0], 1u);
+ atomicExchange(&test_locations.value[y_1], 2u);
+ storageBarrier();
+ let r0 = atomicLoad(&test_locations.value[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
+g.test('read').
+desc(
+ `In the read litmus test, one thread writes 1 to memory location x and then 1 to memory location y. A second thread
+ first writes 2 to y and then reads from x. If the value read by the second thread is 0 but the value in memory of y
+ after the test completes is 2, then there has been some re-ordering of instructions disallowed when using WebGPU's
+ barrier. Additionally, both writes to y are RMWs, so that the barrier forces the correct acquire/release memory ordering
+ synchronization.
+ `
+).
+paramsSimple([
+{ memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryReadTestCode },
+{ memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryReadTestCode }]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(
+ t.params._testCode,
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const messagePassingResultShader = buildResultShader(
+ `
+ if ((r0 == 1u && mem_y_0 == 2u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 0u && mem_y_0 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 1u && mem_y_0 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 0u && mem_y_0 == 2u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `,
+ TestType.IntraWorkgroup,
+ ResultType.FourBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ messagePassingResultShader
+ );
+ await memModelTester.run(40, 3);
+});
+
+const workgroupMemoryStoreBufferTestCode = `
+ atomicStore(&wg_test_locations[x_0], 1u);
+ workgroupBarrier();
+ let r0 = atomicAdd(&wg_test_locations[y_0], 0u);
+ atomicExchange(&wg_test_locations[y_1], 1u);
+ workgroupBarrier();
+ let r1 = atomicLoad(&wg_test_locations[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+const storageMemoryStoreBufferTestCode = `
+ atomicStore(&test_locations.value[x_0], 1u);
+ storageBarrier();
+ let r0 = atomicAdd(&test_locations.value[y_0], 0u);
+ atomicExchange(&test_locations.value[y_1], 1u);
+ storageBarrier();
+ let r1 = atomicLoad(&test_locations.value[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
+`;
+
+g.test('store_buffer').
+desc(
+ `In the store buffer litmus test, one thread writes 1 to memory location x and then reads from memory location
+ y. A second thread writes 1 to y and then reads from x. If both reads return 0, then stores have been buffered
+ or some other re-ordering has occurred that is disallowed by WebGPU's barriers. Additionally, both the read
+ and store to y are RMWs to achieve the necessary synchronization across threads.
+ `
+).
+paramsSimple([
+{ memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryStoreBufferTestCode },
+{ memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryStoreBufferTestCode }]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(
+ t.params._testCode,
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const messagePassingResultShader = buildResultShader(
+ `
+ if ((r0 == 1u && r1 == 0u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((r0 == 0u && r1 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((r0 == 1u && r1 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((r0 == 0u && r1 == 0u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `,
+ TestType.IntraWorkgroup,
+ ResultType.FourBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ messagePassingResultShader
+ );
+ await memModelTester.run(40, 3);
+});
+
+const workgroupMemory2P2WTestCode = `
+ atomicStore(&wg_test_locations[x_0], 2u);
+ workgroupBarrier();
+ atomicExchange(&wg_test_locations[y_0], 1u);
+ atomicExchange(&wg_test_locations[y_1], 2u);
+ workgroupBarrier();
+ atomicStore(&wg_test_locations[x_1], 1u);
+ workgroupBarrier();
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
+ atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + y_1], atomicLoad(&wg_test_locations[y_1]));
+`;
+
+const storageMemory2P2WTestCode = `
+ atomicStore(&test_locations.value[x_0], 2u);
+ storageBarrier();
+ atomicExchange(&test_locations.value[y_0], 1u);
+ atomicExchange(&test_locations.value[y_1], 2u);
+ storageBarrier();
+ atomicStore(&test_locations.value[x_1], 1u);
+`;
+
+g.test('2_plus_2_write').
+desc(
+ `In the 2+2 write litmus test, one thread stores 2 to memory location x and then 1 to memory location y.
+ A second thread stores 2 to y and then 1 to x. If at the end of the test both memory locations are set to 2,
+ then some disallowed re-ordering has occurred. Both writes to y are RMWs to achieve the required synchronization.
+ `
+).
+paramsSimple([
+{ memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemory2P2WTestCode },
+{ memType: MemoryType.AtomicStorageClass, _testCode: storageMemory2P2WTestCode }]
+).
+fn(async (t) => {
+ const testShader = buildTestShader(
+ t.params._testCode,
+ t.params.memType,
+ TestType.IntraWorkgroup
+ );
+ const messagePassingResultShader = buildResultShader(
+ `
+ if ((mem_x_0 == 1u && mem_y_0 == 2u)) {
+ atomicAdd(&test_results.seq0, 1u);
+ } else if ((mem_x_0 == 2u && mem_y_0 == 1u)) {
+ atomicAdd(&test_results.seq1, 1u);
+ } else if ((mem_x_0 == 1u && mem_y_0 == 1u)) {
+ atomicAdd(&test_results.interleaved, 1u);
+ } else if ((mem_x_0 == 2u && mem_y_0 == 2u)) {
+ atomicAdd(&test_results.weak, 1u);
+ }
+ `,
+ TestType.IntraWorkgroup,
+ ResultType.FourBehavior
+ );
+ const memModelTester = new MemoryModelTester(
+ t,
+ memoryModelTestParams,
+ testShader,
+ messagePassingResultShader
+ );
+ await memModelTester.run(40, 3);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/padding.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/padding.spec.js
new file mode 100644
index 0000000000..56a64d6084
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/padding.spec.js
@@ -0,0 +1,406 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for preservation of padding bytes in structures and arrays.
+`;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);
+
+/**
+ * Run a shader and check that the buffer output matches expectations.
+ *
+ * @param t The test object
+ * @param wgsl The shader source
+ * @param expected The array of expected values after running the shader
+ */
+function runShaderTest(t, wgsl, expected) {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef words.
+ const outputBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(expected.length, (_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 only the non-padding bytes were modified.
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+g.test('struct_implicit').
+desc(
+ `Test that padding bytes in between structure members are preserved.
+
+ This test defines a structure that has implicit padding and creates a read-write storage
+ buffer with that structure type. The shader assigns the whole variable at once, and we
+ then test that data in the padding bytes was preserved.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ a : u32,
+ // 12 bytes of padding
+ b : vec3<u32>,
+ // 4 bytes of padding
+ c : vec2<u32>,
+ // 8 bytes of padding
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = S(0x12345678, vec3(0xabcdef01), vec2(0x98765432));
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // a : u32
+ 0x12345678, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // b : vec3<u32>
+ 0xabcdef01, 0xabcdef01, 0xabcdef01, 0xdeadbeef,
+ // c : vec2<u32>
+ 0x98765432, 0x98765432, 0xdeadbeef, 0xdeadbeef]
+ )
+ );
+});
+
+g.test('struct_explicit').
+desc(
+ `Test that padding bytes in between structure members are preserved.
+
+ This test defines a structure with explicit padding attributes and creates a read-write storage
+ buffer with that structure type. The shader assigns the whole variable at once, and we
+ then test that data in the padding bytes was preserved.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ a : u32,
+ // 12 bytes of padding
+ @align(16) @size(20) b : u32,
+ // 16 bytes of padding
+ @size(12) c : u32,
+ // 8 bytes of padding
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = S(0x12345678, 0xabcdef01, 0x98765432);
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // a : u32
+ 0x12345678, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // @align(16) @size(20) b : u32
+ 0xabcdef01, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // @size(12) c : u32
+ 0x98765432, 0xdeadbeef, 0xdeadbeef]
+ )
+ );
+});
+
+g.test('struct_nested').
+desc(
+ `Test that padding bytes in nested structures are preserved.
+
+ This test defines a set of nested structures that have padding and creates a read-write storage
+ buffer with the root structure type. The shader assigns the whole variable at once, and we
+ then test that data in the padding bytes was preserved.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ // Size of S1 is 48 bytes.
+ // Alignment of S1 is 16 bytes.
+ struct S1 {
+ a : u32,
+ // 12 bytes of padding
+ b : vec3<u32>,
+ // 4 bytes of padding
+ c : vec2<u32>,
+ // 8 bytes of padding
+ }
+
+ // Size of S2 is 112 bytes.
+ // Alignment of S2 is 48 bytes.
+ struct S2 {
+ a2 : u32,
+ // 12 bytes of padding
+ b2 : S1,
+ c2 : S1,
+ }
+
+ // Size of S3 is 144 bytes.
+ // Alignment of S3 is 48 bytes.
+ struct S3 {
+ a3 : S1,
+ b3 : S2,
+ c3 : S2,
+ }
+
+ @group(0) @binding(0) var<storage, read_write> buffer : S3;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = S3();
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // a3 : S1
+ // a3.a1 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // a3.b1 : vec3<u32>
+ 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
+ // a3.c1 : vec2<u32>
+ 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef,
+
+ // b3 : S2
+ // b3.a2 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // b3.b2 : S1
+ // b3.b2.a1 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // b3.b2.b1 : vec3<u32>
+ 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
+ // b3.b2.c1 : vec2<u32>
+ 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef,
+ // b3.c2 : S1
+ // b3.c2.a1 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // b3.c2.b1 : vec3<u32>
+ 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
+ // b3.c2.c1 : vec2<u32>
+ 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef,
+
+ // c3 : S2
+ // c3.a2 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // c3.b2 : S1
+ // c3.b2.a1 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // c3.b2.b1 : vec3<u32>
+ 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
+ // c3.b2.c1 : vec2<u32>
+ 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef,
+ // c3.c2 : S1
+ // c3.c2.a1 : u32
+ 0x00000000, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+ // c3.c2.b1 : vec3<u32>
+ 0x00000000, 0x00000000, 0x00000000, 0xdeadbeef,
+ // c3.c2.c1 : vec2<u32>
+ 0x00000000, 0x00000000, 0xdeadbeef, 0xdeadbeef]
+ )
+ );
+});
+
+g.test('array_of_vec3').
+desc(
+ `Test that padding bytes in between array elements are preserved.
+
+ This test defines creates a read-write storage buffer with type array<vec3, 4>. The shader
+ assigns the whole variable at once, and we then test that data in the padding bytes was
+ preserved.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read_write> buffer : array<vec3<u32>, 4>;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = array<vec3<u32>, 4>(
+ vec3(0x12345678),
+ vec3(0xabcdef01),
+ vec3(0x98765432),
+ vec3(0x0f0f0f0f),
+ );
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // buffer[0]
+ 0x12345678, 0x12345678, 0x12345678, 0xdeadbeef,
+ // buffer[1]
+ 0xabcdef01, 0xabcdef01, 0xabcdef01, 0xdeadbeef,
+ // buffer[2]
+ 0x98765432, 0x98765432, 0x98765432, 0xdeadbeef,
+ // buffer[2]
+ 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0xdeadbeef]
+ )
+ );
+});
+
+g.test('array_of_struct').
+desc(
+ `Test that padding bytes in between array elements are preserved.
+
+ This test defines creates a read-write storage buffer with type array<S, 4>, where S is a
+ structure that contains padding bytes. The shader assigns the whole variable at once, and we
+ then test that data in the padding bytes was preserved.
+ `
+).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ a : u32,
+ b : vec3<u32>,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : array<S, 3>;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = array<S, 3>(
+ S(0x12345678, vec3(0x0f0f0f0f)),
+ S(0xabcdef01, vec3(0x7c7c7c7c)),
+ S(0x98765432, vec3(0x18181818)),
+ );
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // buffer[0]
+ 0x12345678, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f,
+ 0xdeadbeef,
+ // buffer[1]
+ 0xabcdef01, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x7c7c7c7c, 0x7c7c7c7c, 0x7c7c7c7c,
+ 0xdeadbeef,
+ // buffer[2]
+ 0x98765432, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x18181818, 0x18181818, 0x18181818,
+ 0xdeadbeef]
+ )
+ );
+});
+
+g.test('vec3').
+desc(
+ `Test padding bytes are preserved when assigning to a variable of type vec3 (without a struct).
+ `
+).
+fn((t) => {
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read_write> buffer : vec3<u32>;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ buffer = vec3<u32>(0x12345678, 0xabcdef01, 0x98765432);
+ }
+ `;
+ runShaderTest(t, wgsl, new Uint32Array([0x12345678, 0xabcdef01, 0x98765432, 0xdeadbeef]));
+});
+
+g.test('matCx3').
+desc(
+ `Test padding bytes are preserved when assigning to a variable of type matCx3.
+ `
+).
+params((u) =>
+u.
+combine('columns', [2, 3, 4]).
+combine('use_struct', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const cols = t.params.columns;
+ const wgsl = `
+ alias Mat = mat${cols}x3<f32>;
+ ${t.params.use_struct ? `struct S { m : Mat } alias Type = S;` : `alias Type = Mat;`}
+ @group(0) @binding(0) var<storage, read_write> buffer : Type;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var m : Mat;
+ for (var c = 0u; c < ${cols}; c++) {
+ m[c] = vec3(f32(c*3 + 1), f32(c*3 + 2), f32(c*3 + 3));
+ }
+ buffer = Type(m);
+ }
+ `;
+ const f_values = new Float32Array(cols * 4);
+ const u_values = new Uint32Array(f_values.buffer);
+ for (let c = 0; c < cols; c++) {
+ f_values[c * 4 + 0] = c * 3 + 1;
+ f_values[c * 4 + 1] = c * 3 + 2;
+ f_values[c * 4 + 2] = c * 3 + 3;
+ u_values[c * 4 + 3] = 0xdeadbeef;
+ }
+ runShaderTest(t, wgsl, u_values);
+});
+
+g.test('array_of_matCx3').
+desc(
+ `Test that padding bytes in between array elements are preserved.
+
+ This test defines creates a read-write storage buffer with type array<matCx3<f32>, 4>. The
+ shader assigns the whole variable at once, and we then test that data in the padding bytes was
+ preserved.
+ `
+).
+params((u) =>
+u.
+combine('columns', [2, 3, 4]).
+combine('use_struct', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const cols = t.params.columns;
+ const wgsl = `
+ alias Mat = mat${cols}x3<f32>;
+ ${t.params.use_struct ? `struct S { m : Mat } alias Type = S;` : `alias Type = Mat;`}
+ @group(0) @binding(0) var<storage, read_write> buffer : array<Type, 4>;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var m : Mat;
+ for (var c = 0u; c < ${cols}; c++) {
+ m[c] = vec3(f32(c*3 + 1), f32(c*3 + 2), f32(c*3 + 3));
+ }
+ buffer = array<Type, 4>(Type(m), Type(m * 2), Type(m * 3), Type(m * 4));
+ }
+ `;
+ const f_values = new Float32Array(cols * 4 * 4);
+ const u_values = new Uint32Array(f_values.buffer);
+ for (let i = 0; i < 4; i++) {
+ for (let c = 0; c < cols; c++) {
+ f_values[i * (cols * 4) + c * 4 + 0] = (c * 3 + 1) * (i + 1);
+ f_values[i * (cols * 4) + c * 4 + 1] = (c * 3 + 2) * (i + 1);
+ f_values[i * (cols * 4) + c * 4 + 2] = (c * 3 + 3) * (i + 1);
+ u_values[i * (cols * 4) + c * 4 + 3] = 0xdeadbeef;
+ }
+ }
+ runShaderTest(t, wgsl, u_values);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access.spec.js
new file mode 100644
index 0000000000..8e6cd5c80b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access.spec.js
@@ -0,0 +1,480 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests to check datatype clamping in shaders is correctly implemented for all indexable types
+(vectors, matrices, sized/unsized arrays) visible to shaders in various ways.
+
+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 { GPUTest } from '../../gpu_test.js';
+import { align } from '../../util/math.js';
+import { generateTypes, supportedScalarTypes, supportsAtomics } from '../types.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kMaxU32 = 0xffff_ffff;
+const kMaxI32 = 0x7fff_ffff;
+const kMinI32 = -0x8000_0000;
+
+/**
+ * Wraps the provided source into a harness that checks calling `runTest()` returns 0.
+ *
+ * Non-test bindings are in bind group 1, including:
+ * - `constants.zero`: a dynamically-uniform `0u` value.
+ */
+async function runShaderTest(
+t,
+stage,
+testSource,
+layout,
+testBindings,
+dynamicOffsets)
+{
+ assert(stage === GPUShaderStage.COMPUTE, 'Only know how to deal with compute for now');
+
+ // Contains just zero (for now).
+ const constantsBuffer = t.device.createBuffer({ size: 4, usage: GPUBufferUsage.UNIFORM });
+
+ const resultBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ });
+
+ const source = `
+struct Constants {
+ zero: u32
+};
+@group(1) @binding(0) var<uniform> constants: Constants;
+
+struct Result {
+ value: u32
+};
+@group(1) @binding(1) var<storage, read_write> result: Result;
+
+${testSource}
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = constants.zero; // Ensure constants buffer is statically-accessed
+ result.value = runTest();
+}`;
+
+ t.debug(source);
+ const module = t.device.createShaderModule({ code: source });
+ const pipeline = await t.device.createComputePipelineAsync({
+ layout,
+ compute: { module, entryPoint: 'main' }
+ });
+
+ const group = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(1),
+ entries: [
+ { binding: 0, resource: { buffer: constantsBuffer } },
+ { binding: 1, resource: { buffer: resultBuffer } }]
+
+ });
+
+ const testGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: testBindings
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, testGroup, dynamicOffsets);
+ pass.setBindGroup(1, group);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(resultBuffer, new Uint32Array([0]));
+}
+
+/** Fill an ArrayBuffer with sentinel values, except clear a region to zero. */
+function testFillArrayBuffer(
+array,
+type,
+{ zeroByteStart, zeroByteCount })
+{
+ const constructor = { u32: Uint32Array, i32: Int32Array, 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);
+}
+
+/**
+ * Generate a bunch of indexable types (vec, mat, sized/unsized array) for testing.
+ */
+
+g.test('linear_memory').
+desc(
+ `For each indexable data type (vec, mat, sized/unsized array, of various scalar types), attempts
+ to access (read, write, atomic load/store) a region of memory (buffer or internal) at various
+ (signed/unsigned) indices. Checks that the accesses conform to robust access (OOB reads only
+ return bound memory, OOB writes don't write OOB).
+
+ TODO: Test in/out storage classes.
+ TODO: Test vertex and fragment stages.
+ TODO: Test using a dynamic offset instead of a static offset into uniform/storage bindings.
+ TODO: Test types like vec2<atomic<i32>>, if that's allowed.
+ TODO: Test exprIndexAddon as constexpr.
+ TODO: Test exprIndexAddon as pipeline-overridable constant expression.
+ `
+).
+params((u) =>
+u.
+combineWithParams([
+{ addressSpace: 'storage', storageMode: 'read', access: 'read', dynamicOffset: false },
+{
+ addressSpace: 'storage',
+ storageMode: 'read_write',
+ access: 'read',
+ dynamicOffset: false
+},
+{
+ addressSpace: 'storage',
+ storageMode: 'read_write',
+ access: 'write',
+ dynamicOffset: false
+},
+{ addressSpace: 'storage', storageMode: 'read', access: 'read', dynamicOffset: true },
+{ addressSpace: 'storage', storageMode: 'read_write', access: 'read', dynamicOffset: true },
+{
+ addressSpace: 'storage',
+ storageMode: 'read_write',
+ access: 'write',
+ dynamicOffset: true
+},
+{ addressSpace: 'uniform', access: 'read', dynamicOffset: false },
+{ addressSpace: 'uniform', access: 'read', dynamicOffset: true },
+{ addressSpace: 'private', access: 'read' },
+{ addressSpace: 'private', access: 'write' },
+{ addressSpace: 'function', access: 'read' },
+{ addressSpace: 'function', access: 'write' },
+{ addressSpace: 'workgroup', access: 'read' },
+{ addressSpace: 'workgroup', access: 'write' }]
+).
+combineWithParams([
+{ containerType: 'array' },
+{ containerType: 'matrix' },
+{ containerType: 'vector' }]
+).
+combineWithParams([
+{ shadowingMode: 'none' },
+{ shadowingMode: 'module-scope' },
+{ shadowingMode: 'function-scope' }]
+).
+expand('isAtomic', (p) => supportsAtomics(p) ? [false, true] : [false]).
+beginSubcases().
+expand('baseType', supportedScalarTypes).
+expandWithParams(generateTypes)
+).
+fn(async (t) => {
+ const {
+ addressSpace,
+ storageMode,
+ access,
+ dynamicOffset,
+ isAtomic,
+ containerType,
+ baseType,
+ type,
+ shadowingMode,
+ _kTypeInfo
+ } = t.params;
+
+ assert(_kTypeInfo !== undefined, 'not an indexable type');
+ assert('arrayLength' in _kTypeInfo);
+
+ let usesCanary = false;
+ let globalSource = '';
+ let testFunctionSource = '';
+ const testBufferSize = 512;
+ const bufferBindingOffset = 256;
+ /** Undefined if no buffer binding is needed */
+ let bufferBindingSize = undefined;
+
+ // Declare the data that will be accessed to check robust access, as a buffer or a struct
+ // in the global scope or inside the test function itself.
+ const structDecl = `
+struct S {
+ startCanary: array<u32, 10>,
+ data: ${type},
+ endCanary: array<u32, 10>,
+};`;
+
+ const testGroupBGLEntires = [];
+ switch (addressSpace) {
+ case 'uniform':
+ case 'storage':
+ {
+ assert(_kTypeInfo.layout !== undefined);
+ const layout = _kTypeInfo.layout;
+ bufferBindingSize = align(layout.size, layout.alignment);
+ const qualifiers = addressSpace === 'storage' ? `storage, ${storageMode}` : addressSpace;
+ globalSource += `
+struct TestData {
+ data: ${type},
+};
+@group(0) @binding(0) var<${qualifiers}> s: TestData;`;
+
+ testGroupBGLEntires.push({
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: {
+ type:
+ addressSpace === 'uniform' ?
+ 'uniform' :
+ storageMode === 'read' ?
+ 'read-only-storage' :
+ 'storage',
+ hasDynamicOffset: dynamicOffset
+ }
+ });
+ }
+ break;
+
+ case 'private':
+ case 'workgroup':
+ usesCanary = true;
+ globalSource += structDecl;
+ globalSource += `var<${addressSpace}> s: S;`;
+ break;
+
+ case 'function':
+ usesCanary = true;
+ globalSource += structDecl;
+ testFunctionSource += 'var s: S;';
+ break;
+ }
+
+ // Build the test function that will do the tests.
+
+ // If we use a local canary declared in the shader, initialize it.
+ if (usesCanary) {
+ testFunctionSource += `
+ for (var i = 0u; i < 10u; i = i + 1u) {
+ s.startCanary[i] = 0xFFFFFFFFu;
+ s.endCanary[i] = 0xFFFFFFFFu;
+ }`;
+ }
+
+ /** Returns a different number each time, kind of like a `__LINE__` to ID the failing check. */
+ const nextErrorReturnValue = (() => {
+ let errorReturnValue = 0x1000;
+ return () => {
+ ++errorReturnValue;
+ return `0x${errorReturnValue.toString(16)}u`;
+ };
+ })();
+
+ // This is here, instead of in subcases, so only a single shader is needed to test many modes.
+ for (const indexSigned of [false, true]) {
+ const indicesToTest = indexSigned ?
+ [
+ // Exactly in bounds (should be OK)
+ '0',
+ `${_kTypeInfo.arrayLength} - 1`,
+ // Exactly out of bounds
+ '-1',
+ `${_kTypeInfo.arrayLength}`,
+ // Far out of bounds
+ '-1000000',
+ '1000000',
+ `${kMinI32}`,
+ `${kMaxI32}`] :
+
+ [
+ // Exactly in bounds (should be OK)
+ '0u',
+ `${_kTypeInfo.arrayLength}u - 1u`,
+ // Exactly out of bounds
+ `${_kTypeInfo.arrayLength}u`,
+ // Far out of bounds
+ '1000000u',
+ `${kMaxU32}u`,
+ `${kMaxI32}u`];
+
+
+ const indexTypeLiteral = indexSigned ? '0' : '0u';
+ const indexTypeCast = indexSigned ? 'i32' : 'u32';
+ for (const exprIndexAddon of [
+ '', // No addon
+ ` + ${indexTypeLiteral}`, // Add a literal 0
+ ` + ${indexTypeCast}(constants.zero)` // Add a uniform 0
+ ]) {
+ // Produce the accesses to the variable.
+ for (const indexToTest of indicesToTest) {
+ testFunctionSource += `
+ {
+ let index = (${indexToTest})${exprIndexAddon};`;
+ const exprZeroElement = `${_kTypeInfo.elementBaseType}()`;
+ const exprElement = `s.data[index]`;
+
+ switch (access) {
+ case 'read':
+ {
+ let exprLoadElement = isAtomic ? `atomicLoad(&${exprElement})` : exprElement;
+ if (addressSpace === 'uniform' && containerType === 'array') {
+ // Scalar types will be wrapped in a vec4 to satisfy array element size
+ // requirements for the uniform address space, so we need an additional index
+ // accessor expression.
+ exprLoadElement += '[0]';
+ }
+ let condition = `${exprLoadElement} != ${exprZeroElement}`;
+ if (containerType === 'matrix') condition = `any(${condition})`;
+ testFunctionSource += `
+ if (${condition}) { return ${nextErrorReturnValue()}; }`;
+ }
+ break;
+
+ case 'write':
+ if (isAtomic) {
+ testFunctionSource += `
+ atomicStore(&s.data[index], ${exprZeroElement});`;
+ } else {
+ testFunctionSource += `
+ s.data[index] = ${exprZeroElement};`;
+ }
+ break;
+ }
+ testFunctionSource += `
+ }`;
+ }
+ }
+ }
+
+ // Check that the canaries haven't been modified
+ if (usesCanary) {
+ testFunctionSource += `
+ for (var i = 0u; i < 10u; i = i + 1u) {
+ if (s.startCanary[i] != 0xFFFFFFFFu) {
+ return ${nextErrorReturnValue()};
+ }
+ if (s.endCanary[i] != 0xFFFFFFFFu) {
+ return ${nextErrorReturnValue()};
+ }
+ }`;
+ }
+
+ // Shadowing case declarations
+ let moduleScopeShadowDecls = '';
+ let functionScopeShadowDecls = '';
+
+ switch (shadowingMode) {
+ case 'module-scope':
+ // Shadow the builtins likely used by robustness as module-scope variables
+ moduleScopeShadowDecls = `
+var<private> min = 0;
+var<private> max = 0;
+var<private> arrayLength = 0;
+`;
+ // Make sure that these are referenced by the function.
+ // This ensures that compilers don't strip away unused variables.
+ functionScopeShadowDecls = `
+ _ = min;
+ _ = max;
+ _ = arrayLength;
+`;
+ break;
+ case 'function-scope':
+ // Shadow the builtins likely used by robustness as function-scope variables
+ functionScopeShadowDecls = `
+ let min = 0;
+ let max = 0;
+ let arrayLength = 0;
+`;
+ break;
+ }
+
+ // Run the test
+
+ // First aggregate the test source
+ const testSource = `
+${globalSource}
+${moduleScopeShadowDecls}
+
+fn runTest() -> u32 {
+ ${functionScopeShadowDecls}
+ ${testFunctionSource}
+ return 0u;
+}`;
+
+ const layout = t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ t.device.createBindGroupLayout({
+ entries: testGroupBGLEntires
+ }),
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: {
+ type: 'uniform'
+ }
+ },
+ {
+ binding: 1,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: {
+ type: 'storage'
+ }
+ }]
+
+ })]
+
+ });
+
+ // Run it.
+ if (bufferBindingSize !== undefined && baseType !== 'bool') {
+ const expectedData = new ArrayBuffer(testBufferSize);
+ const bufferBindingEnd = bufferBindingOffset + bufferBindingSize;
+ testFillArrayBuffer(expectedData, baseType, {
+ zeroByteStart: bufferBindingOffset,
+ zeroByteCount: bufferBindingSize
+ });
+
+ // Create a buffer that contains zeroes in the allowed access area, and 42s everywhere else.
+ const testBuffer = t.makeBufferWithContents(
+ new Uint8Array(expectedData),
+ GPUBufferUsage.COPY_SRC |
+ GPUBufferUsage.UNIFORM |
+ GPUBufferUsage.STORAGE |
+ GPUBufferUsage.COPY_DST
+ );
+
+ // Run the shader, accessing the buffer.
+ await runShaderTest(
+ t,
+ GPUShaderStage.COMPUTE,
+ testSource,
+ layout,
+ [
+ {
+ binding: 0,
+ resource: {
+ buffer: testBuffer,
+ offset: dynamicOffset ? 0 : bufferBindingOffset,
+ size: bufferBindingSize
+ }
+ }],
+
+ dynamicOffset ? [bufferBindingOffset] : undefined
+ );
+
+ // Check that content of the buffer outside of the allowed area didn't change.
+ const expectedBytes = new Uint8Array(expectedData);
+ t.expectGPUBufferValuesEqual(testBuffer, expectedBytes.subarray(0, bufferBindingOffset), 0);
+ t.expectGPUBufferValuesEqual(
+ testBuffer,
+ expectedBytes.subarray(bufferBindingEnd, testBufferSize),
+ bufferBindingEnd
+ );
+ } else {
+ await runShaderTest(t, GPUShaderStage.COMPUTE, testSource, layout, []);
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js
new file mode 100644
index 0000000000..d632179008
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/robust_access_vertex.spec.js
@@ -0,0 +1,607 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Test vertex attributes behave correctly (no crash / data leak) when accessed out of bounds
+
+Test coverage:
+
+The following is parameterized (all combinations tested):
+
+1) Draw call type? (drawIndexed, drawIndirect, drawIndexedIndirect)
+ - Run the draw call using an index buffer and/or an indirect buffer.
+ - Doesn't test direct draw, as vertex buffer OOB are CPU validated and treated as validation errors.
+ - Also the instance step mode vertex buffer OOB are CPU validated for drawIndexed, so we only test
+ robustness access for vertex step mode vertex buffers.
+
+2) Draw call parameter (vertexCount, firstVertex, indexCount, firstIndex, baseVertex, instanceCount,
+ vertexCountInIndexBuffer)
+ - The parameter which goes out of bounds. Filtered depending on the draw call type.
+ - vertexCount, firstVertex: used for drawIndirect only, test for vertex step mode buffer OOB
+ - instanceCount: used for both drawIndirect and drawIndexedIndirect, test for instance step mode buffer OOB
+ - baseVertex, vertexCountInIndexBuffer: used for both drawIndexed and drawIndexedIndirect, test
+ for vertex step mode buffer OOB. vertexCountInIndexBuffer indicates how many vertices are used
+ within the index buffer, i.e. [0, 1, ..., vertexCountInIndexBuffer-1].
+ - indexCount, firstIndex: used for drawIndexedIndirect only, validate the vertex buffer access
+ when the vertex itself is OOB in index buffer. This never happens in drawIndexed as we have index
+ buffer OOB CPU validation for it.
+
+3) Attribute type (float32, float32x2, float32x3, float32x4)
+ - The input attribute type in the vertex shader
+
+4) Error scale (0, 1, 4, 10^2, 10^4, 10^6)
+ - Offset to add to the correct draw call parameter
+ - 0 For control case
+
+5) Additional vertex buffers (0, +4)
+ - Tests that no OOB occurs if more vertex buffers are used
+
+6) Partial last number and offset vertex buffer (false, true)
+ - Tricky cases that make vertex buffer OOB.
+ - With partial last number enabled, vertex buffer size will be 1 byte less than enough, making the
+ last vertex OOB with 1 byte.
+ - Offset vertex buffer will bind the vertex buffer to render pass with 4 bytes offset, causing OOB
+ - For drawIndexed, these two flags are suppressed for instance step mode vertex buffer to make sure
+ it pass the CPU validation.
+
+The tests have one instance step mode vertex buffer bound for instanced attributes, to make sure
+instanceCount / firstInstance are tested.
+
+The tests include multiple attributes per vertex buffer.
+
+The vertex buffers are filled by repeating a few values randomly chosen for each test until the
+end of the buffer.
+
+The tests run a render pipeline which verifies the following:
+1) All vertex attribute values occur in the buffer or are 0 (for control case it can't be 0)
+2) All gl_VertexIndex values are within the index buffer or 0
+
+TODO:
+Currently firstInstance is not tested, as for drawIndexed it is CPU validated, and for drawIndirect
+and drawIndexedIndirect it should always be 0. Once there is an extension to allow making them non-zero,
+it should be added into drawCallTestParameter list.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert } from '../../../common/util/util.js';
+import { GPUTest, TextureTestMixin } from '../../gpu_test.js';
+
+// Encapsulates a draw call (either indexed or non-indexed)
+class DrawCall {
+
+
+
+ // Add a float offset when binding vertex buffer
+
+
+ // Keep instance step mode vertex buffer in range, in order to test vertex step
+ // mode buffer OOB in drawIndexed. Setting true will suppress partialLastNumber
+ // and offsetVertexBuffer for instance step mode vertex buffer.
+
+
+ // Draw
+
+
+
+ // DrawIndexed
+ // For generating index buffer in drawIndexed and drawIndexedIndirect
+ // For accessing index buffer in drawIndexed and drawIndexedIndirect
+
+
+
+ // Both Draw and DrawIndexed
+
+
+
+ constructor({
+ test,
+ vertexArrays,
+ vertexCount,
+ partialLastNumber,
+ offsetVertexBuffer,
+ keepInstanceStepModeBufferInRange
+
+
+
+
+
+
+
+ }) {
+ this.test = test;
+
+ // Default arguments (valid call)
+ this.vertexCount = vertexCount;
+ this.firstVertex = 0;
+ this.vertexCountInIndexBuffer = vertexCount;
+ this.indexCount = vertexCount;
+ this.firstIndex = 0;
+ this.baseVertex = 0;
+ this.instanceCount = vertexCount;
+ this.firstInstance = 0;
+
+ this.offsetVertexBuffer = offsetVertexBuffer;
+ this.keepInstanceStepModeBufferInRange = keepInstanceStepModeBufferInRange;
+
+ // Since vertexInIndexBuffer is mutable, generation of the index buffer should be deferred to right before calling draw
+
+ // Generate vertex buffer
+ this.vertexBuffers = vertexArrays.map((v, i) => {
+ if (i === 0 && keepInstanceStepModeBufferInRange) {
+ // Suppress partialLastNumber for the first vertex buffer, aka the instance step mode buffer
+ return this.generateVertexBuffer(v, false);
+ } else {
+ return this.generateVertexBuffer(v, partialLastNumber);
+ }
+ });
+ }
+
+ // Insert a draw call into |pass| with specified type
+ insertInto(pass, indexed, indirect) {
+ if (indexed) {
+ if (indirect) {
+ this.drawIndexedIndirect(pass);
+ } else {
+ this.drawIndexed(pass);
+ }
+ } else {
+ if (indirect) {
+ this.drawIndirect(pass);
+ } else {
+ this.draw(pass);
+ }
+ }
+ }
+
+ // Insert a draw call into |pass|
+ draw(pass) {
+ this.bindVertexBuffers(pass);
+ pass.draw(this.vertexCount, this.instanceCount, this.firstVertex, this.firstInstance);
+ }
+
+ // Insert an indexed draw call into |pass|
+ drawIndexed(pass) {
+ // Generate index buffer
+ const indexArray = new Uint32Array(this.vertexCountInIndexBuffer).map((_, i) => i);
+ const indexBuffer = this.test.makeBufferWithContents(indexArray, GPUBufferUsage.INDEX);
+ this.bindVertexBuffers(pass);
+ pass.setIndexBuffer(indexBuffer, 'uint32');
+ pass.drawIndexed(
+ this.indexCount,
+ this.instanceCount,
+ this.firstIndex,
+ this.baseVertex,
+ this.firstInstance
+ );
+ }
+
+ // Insert an indirect draw call into |pass|
+ drawIndirect(pass) {
+ this.bindVertexBuffers(pass);
+ pass.drawIndirect(this.generateIndirectBuffer(), 0);
+ }
+
+ // Insert an indexed indirect draw call into |pass|
+ drawIndexedIndirect(pass) {
+ // Generate index buffer
+ const indexArray = new Uint32Array(this.vertexCountInIndexBuffer).map((_, i) => i);
+ const indexBuffer = this.test.makeBufferWithContents(indexArray, GPUBufferUsage.INDEX);
+ this.bindVertexBuffers(pass);
+ pass.setIndexBuffer(indexBuffer, 'uint32');
+ pass.drawIndexedIndirect(this.generateIndexedIndirectBuffer(), 0);
+ }
+
+ // Bind all vertex buffers generated
+ bindVertexBuffers(pass) {
+ let currSlot = 0;
+ for (let i = 0; i < this.vertexBuffers.length; i++) {
+ if (i === 0 && this.keepInstanceStepModeBufferInRange) {
+ // Keep the instance step mode buffer in range
+ pass.setVertexBuffer(currSlot++, this.vertexBuffers[i], 0);
+ } else {
+ pass.setVertexBuffer(currSlot++, this.vertexBuffers[i], this.offsetVertexBuffer ? 4 : 0);
+ }
+ }
+ }
+
+ // Create a vertex buffer from |vertexArray|
+ // If |partialLastNumber| is true, delete one byte off the end
+ generateVertexBuffer(vertexArray, partialLastNumber) {
+ let size = vertexArray.byteLength;
+ let length = vertexArray.length;
+ if (partialLastNumber) {
+ size -= 1; // Shave off one byte from the buffer size.
+ length -= 1; // And one whole element from the writeBuffer.
+ }
+ const buffer = this.test.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST // Ensure that buffer can be used by writeBuffer
+ });
+ this.test.device.queue.writeBuffer(buffer, 0, vertexArray.slice(0, length));
+ return buffer;
+ }
+
+ // Create an indirect buffer containing draw call values
+ generateIndirectBuffer() {
+ const indirectArray = new Int32Array([
+ this.vertexCount,
+ this.instanceCount,
+ this.firstVertex,
+ this.firstInstance]
+ );
+ return this.test.makeBufferWithContents(indirectArray, GPUBufferUsage.INDIRECT);
+ }
+
+ // Create an indirect buffer containing indexed draw call values
+ generateIndexedIndirectBuffer() {
+ const indirectArray = new Int32Array([
+ this.indexCount,
+ this.instanceCount,
+ this.firstIndex,
+ this.baseVertex,
+ this.firstInstance]
+ );
+ return this.test.makeBufferWithContents(indirectArray, GPUBufferUsage.INDIRECT);
+ }
+}
+
+// Parameterize different sized types
+
+
+
+
+
+
+const typeInfoMap = {
+ float32: {
+ wgslType: 'f32',
+ sizeInBytes: 4,
+ validationFunc: 'return valid(v);'
+ },
+ float32x2: {
+ wgslType: 'vec2<f32>',
+ sizeInBytes: 8,
+ validationFunc: 'return valid(v.x) && valid(v.y);'
+ },
+ float32x3: {
+ wgslType: 'vec3<f32>',
+ sizeInBytes: 12,
+ validationFunc: 'return valid(v.x) && valid(v.y) && valid(v.z);'
+ },
+ float32x4: {
+ wgslType: 'vec4<f32>',
+ sizeInBytes: 16,
+ validationFunc: `return (valid(v.x) && valid(v.y) && valid(v.z) && valid(v.w)) ||
+ (v.x == 0.0 && v.y == 0.0 && v.z == 0.0 && (v.w == 0.0 || v.w == 1.0));`
+ }
+};
+
+class F extends TextureTestMixin(GPUTest) {
+ generateBufferContents(
+ numVertices,
+ attributesPerBuffer,
+ typeInfo,
+ arbitraryValues,
+ bufferCount)
+ {
+ // Make an array big enough for the vertices, attributes, and size of each element
+ const vertexArray = new Float32Array(
+ numVertices * attributesPerBuffer * (typeInfo.sizeInBytes / 4)
+ );
+
+ for (let i = 0; i < vertexArray.length; ++i) {
+ vertexArray[i] = arbitraryValues[i % arbitraryValues.length];
+ }
+
+ // Only the first buffer is instance step mode, all others are vertex step mode buffer
+ assert(bufferCount >= 2);
+ const bufferContents = [];
+ for (let i = 0; i < bufferCount; i++) {
+ bufferContents.push(vertexArray);
+ }
+
+ return bufferContents;
+ }
+
+ generateVertexBufferDescriptors(
+ bufferCount,
+ attributesPerBuffer,
+ format)
+ {
+ const typeInfo = typeInfoMap[format];
+ // Vertex buffer descriptors
+ const buffers = [];
+ {
+ let currAttribute = 0;
+ for (let i = 0; i < bufferCount; i++) {
+ buffers.push({
+ arrayStride: attributesPerBuffer * typeInfo.sizeInBytes,
+ stepMode: i === 0 ? 'instance' : 'vertex',
+ attributes: Array(attributesPerBuffer).
+ fill(0).
+ map((_, i) => ({
+ shaderLocation: currAttribute++,
+ offset: i * typeInfo.sizeInBytes,
+ format
+ }))
+ });
+ }
+ }
+ return buffers;
+ }
+
+ generateVertexShaderCode({
+ bufferCount,
+ attributesPerBuffer,
+ validValues,
+ typeInfo,
+ vertexIndexOffset,
+ numVertices,
+ isIndexed
+
+
+
+
+
+
+
+
+ }) {
+ // Create layout and attributes listing
+ let layoutStr = 'struct Attributes {';
+ const attributeNames = [];
+ {
+ let currAttribute = 0;
+ for (let i = 0; i < bufferCount; i++) {
+ for (let j = 0; j < attributesPerBuffer; j++) {
+ layoutStr += `@location(${currAttribute}) a_${currAttribute} : ${typeInfo.wgslType},\n`;
+ attributeNames.push(`a_${currAttribute}`);
+ currAttribute++;
+ }
+ }
+ }
+ layoutStr += '};';
+
+ const vertexShaderCode = `
+ ${layoutStr}
+
+ fn valid(f : f32) -> bool {
+ return ${validValues.map((v) => `f == ${v}.0`).join(' || ')};
+ }
+
+ fn validationFunc(v : ${typeInfo.wgslType}) -> bool {
+ ${typeInfo.validationFunc}
+ }
+
+ @vertex fn main(
+ @builtin(vertex_index) VertexIndex : u32,
+ attributes : Attributes
+ ) -> @builtin(position) vec4<f32> {
+ var attributesInBounds = ${attributeNames.
+ map((a) => `validationFunc(attributes.${a})`).
+ join(' && ')};
+
+ var indexInBoundsCountFromBaseVertex =
+ (VertexIndex >= ${vertexIndexOffset}u &&
+ VertexIndex < ${vertexIndexOffset + numVertices}u);
+ var indexInBounds = VertexIndex == 0u || indexInBoundsCountFromBaseVertex;
+
+ var Position : vec4<f32>;
+ if (attributesInBounds && (${!isIndexed} || indexInBounds)) {
+ // Success case, move the vertex to the right of the viewport to show that at least one case succeed
+ Position = vec4<f32>(0.5, 0.0, 0.0, 1.0);
+ } else {
+ // Failure case, move the vertex to the left of the viewport
+ Position = vec4<f32>(-0.5, 0.0, 0.0, 1.0);
+ }
+ return Position;
+ }`;
+ return vertexShaderCode;
+ }
+
+ createRenderPipeline({
+ bufferCount,
+ attributesPerBuffer,
+ validValues,
+ typeInfo,
+ vertexIndexOffset,
+ numVertices,
+ isIndexed,
+ buffers
+
+
+
+
+
+
+
+
+
+ }) {
+ const pipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: this.generateVertexShaderCode({
+ bufferCount,
+ attributesPerBuffer,
+ validValues,
+ typeInfo,
+ vertexIndexOffset,
+ numVertices,
+ isIndexed
+ })
+ }),
+ entryPoint: 'main',
+ buffers
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: `
+ @fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 0.0, 0.0, 1.0);
+ }`
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }]
+ },
+ primitive: { topology: 'point-list' }
+ });
+ return pipeline;
+ }
+
+ doTest({
+ bufferCount,
+ attributesPerBuffer,
+ dataType,
+ validValues,
+ vertexIndexOffset,
+ numVertices,
+ isIndexed,
+ isIndirect,
+ drawCall
+
+
+
+
+
+
+
+
+
+
+ }) {
+ // Vertex buffer descriptors
+ const buffers = this.generateVertexBufferDescriptors(
+ bufferCount,
+ attributesPerBuffer,
+ dataType
+ );
+
+ // Pipeline setup, texture setup
+ const pipeline = this.createRenderPipeline({
+ bufferCount,
+ attributesPerBuffer,
+ validValues,
+ typeInfo: typeInfoMap[dataType],
+ vertexIndexOffset,
+ numVertices,
+ isIndexed,
+ buffers
+ });
+
+ const colorAttachment = this.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 2, height: 1, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const colorAttachmentView = colorAttachment.createView();
+
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ storeOp: 'store',
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+
+ // Run the draw variant
+ drawCall.insertInto(pass, isIndexed, isIndirect);
+
+ pass.end();
+ this.device.queue.submit([encoder.finish()]);
+
+ // Validate we see green on the left pixel, showing that no failure case is detected
+ this.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
+ { coord: { x: 0, y: 0 }, exp: new Uint8Array([0x00, 0xff, 0x00, 0xff]) }]
+ );
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('vertex_buffer_access').
+params(
+ (u) =>
+ u.
+ combineWithParams([
+ { indexed: false, indirect: true },
+ { indexed: true, indirect: false },
+ { indexed: true, indirect: true }]
+ ).
+ expand('drawCallTestParameter', function* (p) {
+ if (p.indexed) {
+ yield* ['baseVertex', 'vertexCountInIndexBuffer'];
+ if (p.indirect) {
+ yield* ['indexCount', 'instanceCount', 'firstIndex'];
+ }
+ } else if (p.indirect) {
+ yield* ['vertexCount', 'instanceCount', 'firstVertex'];
+ }
+ }).
+ combine('type', Object.keys(typeInfoMap)).
+ combine('additionalBuffers', [0, 4]).
+ combine('partialLastNumber', [false, true]).
+ combine('offsetVertexBuffer', [false, true]).
+ combine('errorScale', [0, 1, 4, 10 ** 2, 10 ** 4, 10 ** 6]).
+ unless((p) => p.drawCallTestParameter === 'instanceCount' && p.errorScale > 10 ** 4) // To avoid timeout
+).
+fn((t) => {
+ const p = t.params;
+ const typeInfo = typeInfoMap[p.type];
+
+ // Number of vertices to draw
+ const numVertices = 4;
+ // Each buffer is bound to this many attributes (2 would mean 2 attributes per buffer)
+ const attributesPerBuffer = 2;
+ // Some arbitrary values to fill our buffer with to avoid collisions with other tests
+ const arbitraryValues = [990, 685, 446, 175];
+
+ // A valid value is 0 or one in the buffer
+ const validValues =
+ p.errorScale === 0 && !p.offsetVertexBuffer && !p.partialLastNumber ?
+ arbitraryValues // Control case with no OOB access, must read back valid values in buffer
+ : [0, ...arbitraryValues]; // Testing case with OOB access, can be 0 for OOB data
+
+ // Generate vertex buffer contents. Only the first buffer is instance step mode, all others are vertex step mode
+ const bufferCount = p.additionalBuffers + 2; // At least one instance step mode and one vertex step mode buffer
+ const bufferContents = t.generateBufferContents(
+ numVertices,
+ attributesPerBuffer,
+ typeInfo,
+ arbitraryValues,
+ bufferCount
+ );
+
+ // Mutable draw call
+ const draw = new DrawCall({
+ test: t,
+ vertexArrays: bufferContents,
+ vertexCount: numVertices,
+ partialLastNumber: p.partialLastNumber,
+ offsetVertexBuffer: p.offsetVertexBuffer,
+ keepInstanceStepModeBufferInRange: p.indexed && !p.indirect // keep instance step mode buffer in range for drawIndexed
+ });
+
+ // Offset the draw call parameter we are testing by |errorScale|
+ draw[p.drawCallTestParameter] += p.errorScale;
+ // Offset the range checks for gl_VertexIndex in the shader if we use BaseVertex
+ let vertexIndexOffset = 0;
+ if (p.drawCallTestParameter === 'baseVertex') {
+ vertexIndexOffset += p.errorScale;
+ }
+
+ t.doTest({
+ bufferCount,
+ attributesPerBuffer,
+ dataType: p.type,
+ validValues,
+ vertexIndexOffset,
+ numVertices,
+ isIndexed: p.indexed,
+ isIndirect: p.indirect,
+ drawCall: draw
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/compute_builtins.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/compute_builtins.spec.js
new file mode 100644
index 0000000000..68eebacc05
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/compute_builtins.spec.js
@@ -0,0 +1,297 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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);
+
+// Test that the values for each input builtin are correct.
+g.test('inputs').
+desc(`Test compute shader builtin inputs values`).
+params((u) =>
+u.
+combine('method', ['param', 'struct', 'mixed']).
+combine('dispatch', ['direct', 'indirect']).
+combineWithParams([
+{
+ groupSize: { x: 1, y: 1, z: 1 },
+ numGroups: { x: 1, y: 1, z: 1 }
+},
+{
+ groupSize: { x: 8, y: 4, z: 2 },
+ numGroups: { x: 1, y: 1, z: 1 }
+},
+{
+ groupSize: { x: 1, y: 1, z: 1 },
+ numGroups: { x: 8, y: 4, z: 2 }
+},
+{
+ groupSize: { x: 3, y: 7, z: 5 },
+ numGroups: { x: 13, y: 9, z: 11 }
+}]
+).
+beginSubcases()
+).
+fn((t) => {
+ const invocationsPerGroup = t.params.groupSize.x * t.params.groupSize.y * t.params.groupSize.z;
+ const totalInvocations =
+ invocationsPerGroup * t.params.numGroups.x * t.params.numGroups.y * t.params.numGroups.z;
+
+ // Generate the structures, parameters, and builtin expressions used in the shader.
+ let params = '';
+ let structures = '';
+ let local_id = '';
+ let local_index = '';
+ let global_id = '';
+ let group_id = '';
+ let num_groups = '';
+ switch (t.params.method) {
+ case 'param':
+ params = `
+ @builtin(local_invocation_id) local_id : vec3<u32>,
+ @builtin(local_invocation_index) local_index : u32,
+ @builtin(global_invocation_id) global_id : vec3<u32>,
+ @builtin(workgroup_id) group_id : vec3<u32>,
+ @builtin(num_workgroups) num_groups : vec3<u32>,
+ `;
+ local_id = 'local_id';
+ local_index = 'local_index';
+ global_id = 'global_id';
+ group_id = 'group_id';
+ num_groups = 'num_groups';
+ break;
+ case 'struct':
+ structures = `struct Inputs {
+ @builtin(local_invocation_id) local_id : vec3<u32>,
+ @builtin(local_invocation_index) local_index : u32,
+ @builtin(global_invocation_id) global_id : vec3<u32>,
+ @builtin(workgroup_id) group_id : vec3<u32>,
+ @builtin(num_workgroups) num_groups : vec3<u32>,
+ };`;
+ params = `inputs : Inputs`;
+ local_id = 'inputs.local_id';
+ local_index = 'inputs.local_index';
+ global_id = 'inputs.global_id';
+ group_id = 'inputs.group_id';
+ num_groups = 'inputs.num_groups';
+ break;
+ case 'mixed':
+ structures = `struct InputsA {
+ @builtin(local_invocation_index) local_index : u32,
+ @builtin(global_invocation_id) global_id : vec3<u32>,
+ };
+ struct InputsB {
+ @builtin(workgroup_id) group_id : vec3<u32>
+ };`;
+ params = `@builtin(local_invocation_id) local_id : vec3<u32>,
+ inputsA : InputsA,
+ inputsB : InputsB,
+ @builtin(num_workgroups) num_groups : vec3<u32>,`;
+ local_id = 'local_id';
+ local_index = 'inputsA.local_index';
+ global_id = 'inputsA.global_id';
+ group_id = 'inputsB.group_id';
+ num_groups = 'num_groups';
+ break;
+ }
+
+ // WGSL shader that stores every builtin value to a buffer, for every invocation in the grid.
+ const wgsl = `
+ struct S {
+ data : array<u32>
+ };
+ 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;
+
+ ${structures}
+
+ const group_width = ${t.params.groupSize.x}u;
+ const group_height = ${t.params.groupSize.y}u;
+ const group_depth = ${t.params.groupSize.z}u;
+
+ @compute @workgroup_size(group_width, group_height, group_depth)
+ fn main(
+ ${params}
+ ) {
+ 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};
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: wgsl
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Helper to create a `size`-byte buffer with binding number `binding`.
+ function createBuffer(size, binding) {
+ const buffer = t.device.createBuffer({
+ size,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(buffer);
+
+ bindGroupEntries.push({
+ binding,
+ resource: {
+ buffer
+ }
+ });
+
+ return buffer;
+ }
+
+ // Create the output buffers.
+ const bindGroupEntries = [];
+ 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 bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ switch (t.params.dispatch) {
+ case 'direct':
+ pass.dispatchWorkgroups(t.params.numGroups.x, t.params.numGroups.y, t.params.numGroups.z);
+ break;
+ case 'indirect':{
+ const dispatchBuffer = t.device.createBuffer({
+ size: 3 * Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.INDIRECT,
+ mappedAtCreation: true
+ });
+ t.trackForCleanup(dispatchBuffer);
+ const dispatchData = new Uint32Array(dispatchBuffer.getMappedRange());
+ dispatchData[0] = t.params.numGroups.x;
+ dispatchData[1] = t.params.numGroups.y;
+ dispatchData[2] = t.params.numGroups.z;
+ dispatchBuffer.unmap();
+ pass.dispatchWorkgroupsIndirect(dispatchBuffer, 0);
+ break;
+ }
+ }
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+
+
+ // 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,
+ name,
+ getBuiltinValue) =>
+ {
+ // Loop over workgroups.
+ for (let gz = 0; gz < t.params.numGroups.z; gz++) {
+ for (let gy = 0; gy < t.params.numGroups.y; gy++) {
+ for (let gx = 0; gx < t.params.numGroups.x; gx++) {
+ // Loop over invocations within a group.
+ for (let lz = 0; lz < t.params.groupSize.z; lz++) {
+ for (let ly = 0; ly < t.params.groupSize.y; ly++) {
+ for (let lx = 0; lx < t.params.groupSize.x; lx++) {
+ 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]}`
+ );
+ }
+ 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]}`
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ 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, localId) => {
+ 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 }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/shared_structs.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/shared_structs.spec.js
new file mode 100644
index 0000000000..4582f615ce
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shader_io/shared_structs.spec.js
@@ -0,0 +1,332 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Test the shared use of structures containing entry point IO attributes`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+g.test('shared_with_buffer').
+desc(
+ `Test sharing an entry point IO struct with a buffer.
+
+ This test defines a structure that contains both builtin attributes and layout attributes,
+ and uses that structure as both an entry point input and the store type of a storage buffer.
+ The builtin attributes should be ignored when used for the storage buffer, and the layout
+ attributes should be ignored when used as an entry point IO parameter.
+ `
+).
+fn((t) => {
+ // Set the dispatch parameters such that we get some interesting (non-zero) built-in variables.
+ const wgsize = new Uint32Array([8, 4, 2]);
+ const numGroups = new Uint32Array([4, 2, 8]);
+
+ // Pick a single invocation to copy the input structure to the output buffer.
+ const targetLocalIndex = 13;
+ const targetGroup = new Uint32Array([2, 1, 5]);
+
+ // The test shader defines a structure that contains members decorated with built-in variable
+ // attributes, and also layout attributes for the storage buffer.
+ const wgsl = `
+ struct S {
+ /* byte offset: 0 */ @size(32) @builtin(workgroup_id) group_id : vec3<u32>,
+ /* byte offset: 32 */ @builtin(local_invocation_index) local_index : u32,
+ /* byte offset: 64 */ @align(64) @builtin(num_workgroups) numGroups : vec3<u32>,
+ };
+
+ @group(0) @binding(0)
+ var<storage, read_write> outputs : S;
+
+ @compute @workgroup_size(${wgsize[0]}, ${wgsize[1]}, ${wgsize[2]})
+ fn main(inputs : S) {
+ if (inputs.group_id.x == ${targetGroup[0]}u &&
+ inputs.group_id.y == ${targetGroup[1]}u &&
+ inputs.group_id.z == ${targetGroup[2]}u &&
+ inputs.local_index == ${targetLocalIndex}u) {
+ outputs = inputs;
+ }
+ }
+ `;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Allocate a buffer to hold the output structure.
+ const bufferNumElements = 32;
+ const outputBuffer = t.device.createBuffer({
+ size: bufferNumElements * Uint32Array.BYTES_PER_ELEMENT,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }]
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(numGroups[0], numGroups[1], numGroups[2]);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check the output values.
+ const checkOutput = (outputs) => {
+ if (checkElementsEqual(outputs.slice(0, 3), targetGroup)) {
+ return new Error(
+ `group_id comparison failed\n` +
+ ` expected: ${targetGroup}\n` +
+ ` got: ${outputs.slice(0, 3)}`
+ );
+ }
+ if (outputs[8] !== targetLocalIndex) {
+ return new Error(
+ `local_index comparison failed\n` +
+ ` expected: ${targetLocalIndex}\n` +
+ ` got: ${outputs[8]}`
+ );
+ }
+ if (checkElementsEqual(outputs.slice(16, 19), numGroups)) {
+ return new Error(
+ `numGroups comparison failed\n` +
+ ` expected: ${numGroups}\n` +
+ ` got: ${outputs.slice(16, 19)}`
+ );
+ }
+ return undefined;
+ };
+ t.expectGPUBufferValuesPassCheck(outputBuffer, (outputData) => checkOutput(outputData), {
+ type: Uint32Array,
+ typedLength: bufferNumElements
+ });
+});
+
+g.test('shared_between_stages').
+desc(
+ `Test sharing an entry point IO struct between different pipeline stages.
+
+ This test defines an entry point IO structure, and uses it as both the output of a vertex
+ shader and the input to a fragment shader.
+ `
+).
+fn((t) => {
+ const size = [31, 31];
+ const wgsl = `
+ struct Interface {
+ @builtin(position) position : vec4<f32>,
+ @location(0) color : f32,
+ };
+
+ var<private> vertices : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-0.7, -0.7),
+ vec2<f32>( 0.0, 0.7),
+ vec2<f32>( 0.7, -0.7),
+ );
+
+ @vertex
+ fn vert_main(@builtin(vertex_index) index : u32) -> Interface {
+ return Interface(vec4<f32>(vertices[index], 0.0, 1.0), 1.0);
+ }
+
+ @fragment
+ fn frag_main(inputs : Interface) -> @location(0) vec4<f32> {
+ // Toggle red vs green based on the x position.
+ var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ if (inputs.position.x > f32(${size[0] / 2})) {
+ color.r = inputs.color;
+ } else {
+ color.g = inputs.color;
+ }
+ return color;
+ }
+ `;
+
+ // Set up the render pipeline.
+ const module = t.device.createShaderModule({ code: wgsl });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vert_main'
+ },
+ fragment: {
+ module,
+ entryPoint: 'frag_main',
+ targets: [
+ {
+ format: 'rgba8unorm'
+ }]
+
+ }
+ });
+
+ // Draw a red triangle.
+ const renderTarget = t.device.createTexture({
+ size,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ format: 'rgba8unorm'
+ });
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Test a few points to make sure we rendered a half-red/half-green triangle.
+ const redPixel = new Uint8Array([255, 0, 0, 255]);
+ const greenPixel = new Uint8Array([0, 255, 0, 255]);
+ const blackPixel = new Uint8Array([0, 0, 0, 0]);
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: renderTarget }, [
+ // Red pixels
+ { coord: { x: 16, y: 15 }, exp: redPixel },
+ { coord: { x: 16, y: 8 }, exp: redPixel },
+ { coord: { x: 22, y: 20 }, exp: redPixel },
+ // Green pixels
+ { coord: { x: 14, y: 15 }, exp: greenPixel },
+ { coord: { x: 14, y: 8 }, exp: greenPixel },
+ { coord: { x: 8, y: 20 }, exp: greenPixel },
+ // Black pixels
+ { coord: { x: 2, y: 2 }, exp: blackPixel },
+ { coord: { x: 2, y: 28 }, exp: blackPixel },
+ { coord: { x: 28, y: 2 }, exp: blackPixel },
+ { coord: { x: 28, y: 28 }, exp: blackPixel }]
+ );
+});
+
+g.test('shared_with_non_entry_point_function').
+desc(
+ `Test sharing an entry point IO struct with a non entry point function.
+
+ This test defines structures that contain builtin and location attributes, and uses those
+ structures as parameter and return types for entry point functions and regular functions.
+ `
+).
+fn((t) => {
+ // The test shader defines structures that contain members decorated with built-in variable
+ // attributes and user-defined IO. These structures are passed to and returned from regular
+ // functions.
+ const wgsl = `
+ struct Inputs {
+ @builtin(vertex_index) index : u32,
+ @location(0) color : vec4<f32>,
+ };
+ struct Outputs {
+ @builtin(position) position : vec4<f32>,
+ @location(0) color : vec4<f32>,
+ };
+
+ var<private> vertices : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ vec2<f32>(-0.7, -0.7),
+ vec2<f32>( 0.0, 0.7),
+ vec2<f32>( 0.7, -0.7),
+ );
+
+ fn process(in : Inputs) -> Outputs {
+ var out : Outputs;
+ out.position = vec4<f32>(vertices[in.index], 0.0, 1.0);
+ out.color = in.color;
+ return out;
+ }
+
+ @vertex
+ fn vert_main(inputs : Inputs) -> Outputs {
+ return process(inputs);
+ }
+
+ @fragment
+ fn frag_main(@location(0) color : vec4<f32>) -> @location(0) vec4<f32> {
+ return color;
+ }
+ `;
+
+ // Set up the render pipeline.
+ const module = t.device.createShaderModule({ code: wgsl });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vert_main',
+ buffers: [
+ {
+ attributes: [
+ {
+ shaderLocation: 0,
+ format: 'float32x4',
+ offset: 0
+ }],
+
+ arrayStride: 4 * Float32Array.BYTES_PER_ELEMENT
+ }]
+
+ },
+ fragment: {
+ module,
+ entryPoint: 'frag_main',
+ targets: [
+ {
+ format: 'rgba8unorm'
+ }]
+
+ }
+ });
+
+ // Draw a triangle.
+ // The vertex buffer contains the vertex colors (all red).
+ const vertexBuffer = t.makeBufferWithContents(
+ new Float32Array([1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0]),
+ GPUBufferUsage.VERTEX
+ );
+ const renderTarget = t.device.createTexture({
+ size: [31, 31],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ format: 'rgba8unorm'
+ });
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.setVertexBuffer(0, vertexBuffer);
+ pass.draw(3);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Test a few points to make sure we rendered a red triangle.
+ const redPixel = new Uint8Array([255, 0, 0, 255]);
+ const blackPixel = new Uint8Array([0, 0, 0, 0]);
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: renderTarget }, [
+ // Red pixels
+ { coord: { x: 15, y: 15 }, exp: redPixel },
+ { coord: { x: 15, y: 8 }, exp: redPixel },
+ { coord: { x: 8, y: 20 }, exp: redPixel },
+ { coord: { x: 22, y: 20 }, exp: redPixel },
+ // Black pixels
+ { coord: { x: 2, y: 2 }, exp: blackPixel },
+ { coord: { x: 2, y: 28 }, exp: blackPixel },
+ { coord: { x: 28, y: 2 }, exp: blackPixel },
+ { coord: { x: 28, y: 28 }, exp: blackPixel }]
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shadow.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shadow.spec.js
new file mode 100644
index 0000000000..1bb04d5509
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/shadow.spec.js
@@ -0,0 +1,406 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Execution Tests for shadowing
+`;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);
+
+/**
+ * Run a shader and check that the buffer output matches expectations.
+ *
+ * @param t The test object
+ * @param wgsl The shader source
+ * @param expected The array of expected values after running the shader
+ */
+function runShaderTest(t, wgsl, expected) {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main'
+ }
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef words.
+ const outputBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(expected.length, (_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 only the non-padding bytes were modified.
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+g.test('declaration').
+desc(`Test that shadowing is handled correctly`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_var_start: u32,
+ my_var_block_shadow: u32,
+ my_var_unshadow: u32,
+ my_var_param_shadow: u32,
+ my_var_param_reshadow: u32,
+ my_var_after_func: u32,
+
+ my_const_start: u32,
+ my_const_block_shadow: u32,
+ my_const_unshadow: u32,
+ my_const_param_shadow: u32,
+ my_const_param_reshadow: u32,
+ my_const_after_func: u32,
+
+ my_let_block_shadow: u32,
+ my_let_param_reshadow: u32,
+ my_let_after_func: u32,
+
+ my_func_param_shadow: u32,
+ my_func_shadow: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ var<private> my_var: u32 = 1;
+ const my_const: u32 = 100;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ let my_let = 200u;
+
+ buffer.my_var_start = my_var; // 1
+ buffer.my_const_start = my_const; // 100
+
+ {
+ var my_var: u32 = 10;
+ const my_const: u32 = 110;
+
+ buffer.my_var_block_shadow = my_var; // 10
+ buffer.my_const_block_shadow = my_const; // 110
+
+ let my_let = 210u;
+ buffer.my_let_block_shadow = my_let; // 210
+ }
+
+ buffer.my_var_unshadow = my_var; // 1
+ buffer.my_const_unshadow = my_const; // 100
+
+ my_func(20, 120, my_let, 300);
+
+ buffer.my_var_after_func = my_var; // 1
+ buffer.my_const_after_func = my_const; // 100
+ buffer.my_let_after_func = my_let; // 200;
+ };
+
+ // Note, defined after |main|
+ fn my_func(my_var: u32, my_const: u32, my_let: u32, my_func: u32) {
+ buffer.my_var_param_shadow = my_var; // 20
+ buffer.my_const_param_shadow = my_const; // 120
+
+ buffer.my_func_param_shadow = my_func; // 300
+
+ // Need block here because of scoping rules for parameters
+ {
+ var my_var = 30u;
+ const my_const = 130u;
+
+ buffer.my_var_param_reshadow = my_var; // 30
+ buffer.my_const_param_reshadow = my_const; // 130
+
+ let my_let = 220u;
+ buffer.my_let_param_reshadow = my_let; // 220
+
+ let my_func: u32 = 310;
+ buffer.my_func_shadow = my_func; // 310
+ }
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // my_var
+ 1, // my_var_start
+ 10, // my_var_block_shadow
+ 1, // my_var_unshadow
+ 20, // my_var_param_shadow
+ 30, // my_var_param_reshadow
+ 1, // my_var_after_func
+ // my_const
+ 100, // my_const_start
+ 110, // my_const_block_shadow
+ 100, // my_const_unshadow
+ 120, // my_const_param_shadow
+ 130, // my_const_param_reshadow
+ 100, // my_const_after_func
+ // my_let
+ 210, // my_let_block_shadow
+ 220, // my_let_param_reshadow
+ 200, // my_let_after_func
+ // my_func
+ 300, // my_func_param_shadow
+ 310 // my_func_shadow
+ ])
+ );
+});
+
+g.test('builtin').
+desc(`Test that shadowing a builtin name is handled correctly`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_max_shadow: u32,
+ max_call: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ let max = 400u;
+ buffer.my_max_shadow = max;
+
+ my_func();
+ };
+
+ fn my_func() {
+ buffer.max_call = max(310u, 410u);
+ }
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ // my_max
+ 400, // my_max_shadow
+ 410 // max_call
+ ])
+ );
+});
+
+g.test('for_loop').
+desc(`Test that shadowing is handled correctly with for loops`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_idx_before: u32,
+ my_idx_loop: array<u32, 2>,
+ my_idx_after: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var my_idx = 500u;
+ buffer.my_idx_before = my_idx; // 500;
+ for (var my_idx = 0u; my_idx < 2u; my_idx++) {
+ let pos = my_idx;
+ var my_idx = 501u + my_idx;
+ buffer.my_idx_loop[pos] = my_idx; // 501, 502
+ }
+ buffer.my_idx_after = my_idx; // 500;
+ };
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ 500, // my_idx_before
+ 501, // my_idx_loop[0]
+ 502, // my_idx_loop[1]
+ 500 // my_idx_after
+ ])
+ );
+});
+
+g.test('while').
+desc(`Test that shadowing is handled correctly with while loops`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_idx_before: u32,
+ my_idx_loop: array<u32, 2>,
+ my_idx_after: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var my_idx = 0u;
+ buffer.my_idx_before = my_idx; // 0;
+
+ var counter = 0u;
+ while (counter < 2) {
+ var my_idx = 500u + counter;
+ buffer.my_idx_loop[counter] = my_idx; // 500, 501
+
+ counter += 1;
+ }
+
+ buffer.my_idx_after = my_idx; // 1;
+ };
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ 0, // my_idx_before
+ 500, // my_idx_loop[0]
+ 501, // my_idx_loop[1]
+ 0 // my_idx_after
+ ])
+ );
+});
+
+g.test('loop').
+desc(`Test that shadowing is handled correctly with loops`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_idx_before: u32,
+ my_idx_loop: array<u32, 2>,
+ my_idx_continuing: array<u32, 2>,
+ my_idx_after: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var my_idx = 0u;
+ buffer.my_idx_before = my_idx; // 0;
+
+ var counter = 0u;
+ loop {
+ var my_idx = 500u + counter;
+ buffer.my_idx_loop[counter] = my_idx; // 500, 501
+
+
+ continuing {
+ var my_idx = 600u + counter;
+ buffer.my_idx_continuing[counter] = my_idx; // 600, 601
+
+ counter += 1;
+ break if counter == 2;
+ }
+ }
+ buffer.my_idx_after = my_idx; // 1;
+ };
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ 0, // my_idx_before
+ 500, // my_idx_loop[0]
+ 501, // my_idx_loop[1]
+ 600, // my_idx_continuing[0]
+ 601, // my_idx_continuing[1]
+ 0 // my_idx_after
+ ])
+ );
+});
+
+g.test('switch').
+desc(`Test that shadowing is handled correctly with a switch`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_idx_before: u32,
+ my_idx_case: u32,
+ my_idx_default: u32,
+ my_idx_after: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var my_idx = 0u;
+ buffer.my_idx_before = my_idx; // 0;
+
+ for (var i = 0; i < 2; i++) {
+ switch (i) {
+ case 0: {
+ var my_idx = 10u;
+ buffer.my_idx_case = my_idx; // 10
+ }
+ default: {
+ var my_idx = 20u;
+ buffer.my_idx_default = my_idx; // 20
+ }
+ }
+ }
+
+ buffer.my_idx_after = my_idx; // 1;
+ };
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ 0, // my_idx_before
+ 10, // my_idx_case
+ 20, // my_idx_default
+ 0 // my_idx_after
+ ])
+ );
+});
+
+g.test('if').
+desc(`Test that shadowing is handled correctly with a switch`).
+fn((t) => {
+ const wgsl = `
+ struct S {
+ my_idx_before: u32,
+ my_idx_if: u32,
+ my_idx_elseif: u32,
+ my_idx_else: u32,
+ my_idx_after: u32,
+ }
+ @group(0) @binding(0) var<storage, read_write> buffer : S;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var my_idx = 0u;
+ buffer.my_idx_before = my_idx; // 0;
+
+ for (var i = 0; i < 3; i++) {
+ if i == 0 {
+ var my_idx = 10u;
+ buffer.my_idx_if = my_idx; // 10
+ } else if i == 1 {
+ var my_idx = 20u;
+ buffer.my_idx_elseif = my_idx; // 20
+ } else {
+ var my_idx = 30u;
+ buffer.my_idx_else = my_idx; // 30
+ }
+ }
+
+ buffer.my_idx_after = my_idx; // 1;
+ };
+ `;
+ runShaderTest(
+ t,
+ wgsl,
+ new Uint32Array([
+ 0, // my_idx_before
+ 10, // my_idx_if
+ 20, // my_idx_elseif
+ 30, // my_idx_else
+ 0 // my_idx_after
+ ])
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/increment_decrement.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/increment_decrement.spec.js
new file mode 100644
index 0000000000..9ed5874283
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/statement/increment_decrement.spec.js
@@ -0,0 +1,381 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Increment and decrement statement tests.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+
+import { GPUTest } from '../../../gpu_test.js';
+import { kValue } from '../../../util/constants.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/**
+ * Builds, runs then checks the output of a statement shader test.
+ *
+ * @param t The test object
+ * @param builder The shader builder function that takes a
+ * StatementTestBuilder as the single argument, and returns either a WGSL
+ * string which is embedded into the WGSL entrypoint function, or a structure
+ * with entrypoint-scoped WGSL code and extra module-scope WGSL code.
+ */
+export function runStatementTest(
+t,
+fmt,
+values,
+wgsl_main)
+{
+ const wgsl = `
+struct Outputs {
+ data : array<${fmt}>,
+};
+var<private> count: u32 = 0;
+
+@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
+
+fn push_output(value : ${fmt}) {
+ 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);
+}
+
+g.test('scalar_i32_increment').
+desc('Tests increment of scalar i32 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-9, 11, kValue.i32.negative.min + 1, kValue.i32.positive.max, 1]),
+ `
+ var a: i32 = -10;
+ var b: i32 = 10;
+ var c: i32 = ${kValue.i32.negative.min};
+ var d: i32 = ${kValue.i32.positive.max - 1};
+ var e: i32 = 0;
+
+ a++;
+ b++;
+ c++;
+ d++;
+ e++;
+
+ push_output(a);
+ push_output(b);
+ push_output(c);
+ push_output(d);
+ push_output(e);
+`
+ );
+});
+
+g.test('scalar_i32_increment_overflow').
+desc('Tests increment of scalar i32 values which overflows').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([kValue.i32.negative.min]),
+ `
+ var a: i32 = ${kValue.i32.positive.max};
+ a++;
+ push_output(a);
+`
+ );
+});
+
+g.test('scalar_u32_increment').
+desc('Tests increment of scalar u32 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'u32',
+ new Uint32Array([1, 11, kValue.u32.max]),
+ `
+ var a: u32 = 0;
+ var b: u32 = 10;
+ var c: u32 = ${kValue.u32.max - 1};
+
+ a++;
+ b++;
+ c++;
+
+ push_output(a);
+ push_output(b);
+ push_output(c);
+`
+ );
+});
+
+g.test('scalar_u32_increment_overflow').
+desc('Tests increment of scalar u32 values which overflows').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'u32',
+ new Uint32Array([0]),
+ `
+ var a: u32 = ${kValue.u32.max};
+ a++;
+ push_output(a);
+`
+ );
+});
+
+g.test('scalar_i32_decrement').
+desc('Tests decrement of scalar i32 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-11, 9, kValue.i32.negative.min, kValue.i32.positive.max - 1, -1]),
+ `
+ var a: i32 = -10;
+ var b: i32 = 10;
+ var c: i32 = ${kValue.i32.negative.min + 1};
+ var d: i32 = ${kValue.i32.positive.max};
+ var e: i32 = 0;
+
+ a--;
+ b--;
+ c--;
+ d--;
+ e--;
+
+ push_output(a);
+ push_output(b);
+ push_output(c);
+ push_output(d);
+ push_output(e);
+`
+ );
+});
+
+g.test('scalar_i32_decrement_underflow').
+desc('Tests decrement of scalar i32 values which underflow').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([kValue.i32.positive.max]),
+ `
+ var a: i32 = ${kValue.i32.negative.min};
+ a--;
+ push_output(a);
+`
+ );
+});
+
+g.test('scalar_u32_decrement').
+desc('Tests decrement of scalar u32 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'u32',
+ new Uint32Array([0, 9, kValue.u32.max - 1]),
+ `
+ var a: u32 = 1;
+ var b: u32 = 10;
+ var c: u32 = ${kValue.u32.max};
+
+ a--;
+ b--;
+ c--;
+
+ push_output(a);
+ push_output(b);
+ push_output(c);
+`
+ );
+});
+
+g.test('scalar_u32_decrement_underflow').
+desc('Tests decrement of scalar u32 values which underflow').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'u32',
+ new Uint32Array([kValue.u32.max]),
+ `
+ var a: u32 = 0;
+ a--;
+ push_output(a);
+`
+ );
+});
+
+g.test('vec2_element_increment').
+desc('Tests increment of ve2 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-9, 11]),
+ `
+ var a = vec2(-10, 10);
+
+ a.x++;
+ a.g++;
+
+ push_output(a.x);
+ push_output(a.y);
+`
+ );
+});
+
+g.test('vec3_element_increment').
+desc('Tests increment of vec3 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-9, 11, kValue.i32.negative.min + 1]),
+ `
+ var a = vec3(-10, 10, ${kValue.i32.negative.min});
+
+ a.x++;
+ a.g++;
+ a.z++;
+
+ push_output(a.x);
+ push_output(a.y);
+ push_output(a.z);
+`
+ );
+});
+
+g.test('vec4_element_increment').
+desc('Tests increment of vec4 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-9, 11, kValue.i32.negative.min + 1, kValue.i32.positive.max]),
+ `
+ var a: vec4<i32> = vec4(-10, 10, ${kValue.i32.negative.min}, ${kValue.i32.positive.max - 1});
+
+ a.x++;
+ a.g++;
+ a.z++;
+ a.a++;
+
+ push_output(a.x);
+ push_output(a.y);
+ push_output(a.z);
+ push_output(a.w);
+`
+ );
+});
+
+g.test('vec2_element_decrement').
+desc('Tests decrement of vec2 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-11, 9]),
+ `
+ var a = vec2(-10, 10);
+
+ a.x--;
+ a.g--;
+
+ push_output(a.x);
+ push_output(a.y);
+`
+ );
+});
+
+g.test('vec3_element_decrement').
+desc('Tests decrement of vec3 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-11, 9, kValue.i32.negative.min]),
+ `
+ var a = vec3(-10, 10, ${kValue.i32.negative.min + 1});
+
+ a.x--;
+ a.g--;
+ a.z--;
+
+ push_output(a.x);
+ push_output(a.y);
+ push_output(a.z);
+`
+ );
+});
+
+g.test('vec4_element_decrement').
+desc('Tests decrement of vec4 values').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([-11, 9, kValue.i32.negative.min, kValue.i32.positive.max - 1]),
+ `
+ var a: vec4<i32> = vec4(-10, 10, ${kValue.i32.negative.min + 1}, ${kValue.i32.positive.max});
+
+ a.x--;
+ a.g--;
+ a.z--;
+ a.a--;
+
+ push_output(a.x);
+ push_output(a.y);
+ push_output(a.z);
+ push_output(a.w);
+`
+ );
+});
+
+g.test('frexp_exp_increment').
+desc('Tests increment can be used on a frexp field').
+fn((t) => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array([2]),
+ `
+ var a = frexp(1.23);
+ a.exp++;
+ push_output(a.exp);
+`
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/zero_init.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/zero_init.spec.js
new file mode 100644
index 0000000000..c7b21d5699
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/execution/zero_init.spec.js
@@ -0,0 +1,546 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Test that variables in the shader are zero initialized`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { iterRange, unreachable } from '../../../common/util/util.js';
+import { GPUTest } from '../../gpu_test.js';
+import {
+
+ kVectorContainerTypes,
+
+ kMatrixContainerTypes,
+
+ supportedScalarTypes,
+ supportsAtomics } from
+'../types.js';
+
+
+
+
+
+
+
+
+
+
+
+function prettyPrint(t) {
+ switch (t.type) {
+ case 'container':
+ switch (t.containerType) {
+ case 'array':
+ return `array<${prettyPrint(t.elementType)}, ${t.length}>`;
+ case 'struct':
+ return `struct { ${t.members.map((m) => prettyPrint(m)).join(', ')} }`;
+ default:
+ return `${t.containerType}<${prettyPrint({
+ type: 'scalar',
+ scalarType: t.scalarType,
+ isAtomic: false
+ })}>`;
+ }
+ break;
+ case 'scalar':
+ if (t.isAtomic) {
+ return `atomic<${t.scalarType}>`;
+ }
+ return t.scalarType;
+ }
+}
+
+export const g = makeTestGroup(GPUTest);
+g.test('compute,zero_init').
+desc(
+ `Test that uninitialized variables in workgroup, private, and function storage classes are initialized to zero.`
+).
+params((u) =>
+u
+// Only workgroup, function, and private variables can be declared without data bound to them.
+// The implementation's shader translator should ensure these values are initialized.
+.combine('addressSpace', ['workgroup', 'private', 'function']).
+expand('workgroupSize', ({ addressSpace }) => {
+ switch (addressSpace) {
+ case 'workgroup':
+ return [
+ [1, 1, 1],
+ [1, 32, 1],
+ [64, 1, 1],
+ [1, 1, 48],
+ [1, 47, 1],
+ [33, 1, 1],
+ [1, 1, 63],
+ [8, 8, 2],
+ [7, 7, 3]];
+
+ case 'function':
+ case 'private':
+ return [[1, 1, 1]];
+ }
+}).
+beginSubcases()
+// Fewer subcases: Only 0 and 2. If double-nested containers work, single-nested should too.
+.combine('_containerDepth', [0, 2]).
+expandWithParams(function* (p) {
+ const kElementCounts = [
+ [], // Not used. Depth 0 is always scalars.
+ [1, 3, 67], // Test something above the workgroup size.
+ [1, 3]];
+
+ const kMemberCounts = [1, 3];
+
+ const memoizedTypes = [];
+
+ function generateTypesMemo(depth) {
+ if (memoizedTypes[depth] === undefined) {
+ memoizedTypes[depth] = Array.from(generateTypes(depth));
+ }
+ return memoizedTypes[depth];
+ }
+
+ function* generateTypes(depth) {
+ if (depth === 0) {
+ for (const isAtomic of supportsAtomics({
+ ...p,
+ access: 'read_write',
+ storageMode: undefined,
+ containerType: 'scalar'
+ }) ?
+ [true, false] :
+ [false]) {
+ for (const scalarType of supportedScalarTypes({ isAtomic, ...p })) {
+ // Fewer subcases: For nested types, skip atomic u32 and non-atomic i32.
+ if (p._containerDepth > 0) {
+ if (scalarType === 'u32' && isAtomic) continue;
+ if (scalarType === 'i32' && !isAtomic) continue;
+ }
+
+ yield {
+ type: 'scalar',
+ scalarType,
+ isAtomic
+ };
+ if (!isAtomic) {
+ // Vector types
+ for (const vectorType of kVectorContainerTypes) {
+ // Fewer subcases: For nested types, only include
+ // vec2<u32>, vec3<i32>, and vec4<f32>
+ if (p._containerDepth > 0) {
+ if (
+ !(
+ vectorType === 'vec2' && scalarType === 'u32' ||
+ vectorType === 'vec3' && scalarType === 'i32' ||
+ vectorType === 'vec4' && scalarType === 'f32'))
+
+ {
+ continue;
+ }
+ }
+ yield {
+ type: 'container',
+ containerType: vectorType,
+ scalarType
+ };
+ }
+ // Matrices can only be f32.
+ if (scalarType === 'f32') {
+ for (const matrixType of kMatrixContainerTypes) {
+ yield {
+ type: 'container',
+ containerType: matrixType,
+ scalarType
+ };
+ }
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ for (const containerType of ['array', 'struct']) {
+ const innerTypes = generateTypesMemo(depth - 1);
+ switch (containerType) {
+ case 'array':
+ for (const elementCount of kElementCounts[depth]) {
+ for (const innerType of innerTypes) {
+ yield {
+ type: 'container',
+ containerType,
+ elementType: innerType,
+ length: elementCount
+ };
+ }
+ }
+ break;
+ case 'struct':
+ for (const memberCount of kMemberCounts) {
+ const memberIndices = new Array(memberCount);
+ for (let m = 0; m < memberCount; ++m) {
+ memberIndices[m] = m;
+ }
+
+ // Don't generate all possible combinations of inner struct members,
+ // because that's in the millions. Instead, just round-robin through
+ // to pick member types. Loop through the types, concatenated forward
+ // and backward, three times to produce a bounded but variable set of
+ // types.
+ const memberTypes = [...innerTypes, ...[...innerTypes].reverse()];
+ const seenTypes = new Set();
+ let typeIndex = 0;
+ while (typeIndex < memberTypes.length * 3) {
+ const prevTypeIndex = typeIndex;
+ const members = [];
+ for (const m of memberIndices) {
+ members[m] = memberTypes[typeIndex % memberTypes.length];
+ typeIndex += 1;
+ }
+
+ const t = {
+ type: 'container',
+ containerType,
+ members
+ };
+ const serializedT = prettyPrint(t);
+ if (seenTypes.has(serializedT)) {
+ // We produced an identical type. shuffle the member indices,
+ // "revert" typeIndex back to where it was before this loop, and
+ // shift it by one. This helps ensure we don't loop forever, and
+ // that we produce a different type on the next iteration.
+ memberIndices.push(memberIndices.shift());
+ typeIndex = prevTypeIndex + 1;
+ continue;
+ }
+ seenTypes.add(serializedT);
+ yield t;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ for (const t of generateTypesMemo(p._containerDepth)) {
+ yield {
+ shaderTypeParam: prettyPrint(t),
+ _type: t
+ };
+ }
+})
+).
+batch(15).
+fn(async (t) => {
+ const { workgroupSize } = t.params;
+ const { maxComputeInvocationsPerWorkgroup } = t.device.limits;
+ const numWorkgroupInvocations = workgroupSize.reduce((a, b) => a * b);
+ t.skipIf(
+ numWorkgroupInvocations > maxComputeInvocationsPerWorkgroup,
+ `workgroupSize: ${workgroupSize} > maxComputeInvocationsPerWorkgroup: ${maxComputeInvocationsPerWorkgroup}`
+ );
+
+ let moduleScope = `
+ struct Output {
+ failed : atomic<u32>
+ }
+ @group(0) @binding(0) var<storage, read_write> output : Output;
+
+ // This uniform value that's a zero is used to prevent the shader compilers from trying to
+ // unroll the massive loops generated by these tests.
+ @group(0) @binding(1) var<uniform> zero : u32;
+ `;
+ let functionScope = '';
+
+ const declaredStructTypes = new Map();
+ const typeDecl = function ensureType(
+ typeName,
+ type,
+ depth = 0)
+ {
+ switch (type.type) {
+ case 'container':
+ switch (type.containerType) {
+ case 'array':
+ return `array<${ensureType(
+ `${typeName}_ArrayElement`,
+ type.elementType,
+ depth + 1
+ )}, ${type.length}>`;
+ case 'struct':{
+ if (declaredStructTypes.has(type)) {
+ return declaredStructTypes.get(type);
+ }
+
+ const members = type.members.
+ map((member, i) => {
+ return `\n member${i} : ${ensureType(
+ `${typeName}_Member${i}`,
+ member,
+ depth + 1
+ )},`;
+ }).
+ join('');
+ declaredStructTypes.set(type, typeName);
+ moduleScope += `\nstruct ${typeName} {`;
+ moduleScope += members;
+ moduleScope += '\n};';
+
+ return typeName;
+ }
+ default:
+ return `${type.containerType}<${ensureType(
+ typeName,
+ {
+ type: 'scalar',
+ scalarType: type.scalarType,
+ isAtomic: false
+ },
+ depth + 1
+ )}>`;
+ }
+ break;
+ case 'scalar':
+ return type.isAtomic ? `atomic<${type.scalarType}>` : type.scalarType;
+ }
+ }('TestType', t.params._type);
+
+ switch (t.params.addressSpace) {
+ case 'workgroup':
+ case 'private':
+ moduleScope += `\nvar<${t.params.addressSpace}> testVar: ${typeDecl};`;
+ break;
+ case 'function':
+ functionScope += `\nvar testVar: ${typeDecl};`;
+ break;
+ }
+
+ const checkZeroCode = function checkZero(
+ value,
+ type,
+ depth = 0)
+ {
+ switch (type.type) {
+ case 'container':
+ switch (type.containerType) {
+ case 'array':
+ return `\nfor (var i${depth} = 0u; i${depth} < ${
+ type.length
+ }u + zero; i${depth} = i${depth} + 1u) {
+ ${checkZero(`${value}[i${depth}]`, type.elementType, depth + 1)}
+ }`;
+ case 'struct':
+ return type.members.
+ map((member, i) => {
+ return checkZero(`${value}.member${i}`, member, depth + 1);
+ }).
+ join('\n');
+ default:
+ if (type.containerType.indexOf('vec') !== -1) {
+ const length = type.containerType[3];
+ return `\nfor (var i${depth} = 0u; i${depth} < ${length}u + zero; i${depth} = i${depth} + 1u) {
+ ${checkZero(
+ `${value}[i${depth}]`,
+ {
+ type: 'scalar',
+ scalarType: type.scalarType,
+ isAtomic: false
+ },
+ depth + 1
+ )}
+ }`;
+ } else if (type.containerType.indexOf('mat') !== -1) {
+ const cols = type.containerType[3];
+ const rows = type.containerType[5];
+ return `\nfor (var c${depth} = 0u; c${depth} < ${cols}u + zero; c${depth} = c${depth} + 1u) {
+ for (var r${depth} = 0u; r${depth} < ${rows}u; r${depth} = r${depth} + 1u) {
+ ${checkZero(
+ `${value}[c${depth}][r${depth}]`,
+ {
+ type: 'scalar',
+ scalarType: type.scalarType,
+ isAtomic: false
+ },
+ depth + 1
+ )}
+ }
+ }`;
+ } else {
+ unreachable();
+ }
+ }
+ break;
+ case 'scalar':{
+ let expected;
+ switch (type.scalarType) {
+ case 'bool':
+ expected = 'false';
+ break;
+ case 'f32':
+ expected = '0.0';
+ break;
+ case 'i32':
+ expected = '0';
+ break;
+ case 'u32':
+ expected = '0u';
+ break;
+ }
+ if (type.isAtomic) {
+ value = `atomicLoad(&${value})`;
+ }
+
+ // Note: this could have an early return, but we omit it because it makes
+ // the tests fail cause with DXGI_ERROR_DEVICE_HUNG on Windows.
+ return `\nif (${value} != ${expected}) { atomicStore(&output.failed, 1u); }`;
+ }
+ }
+ }('testVar', t.params._type);
+
+ const wgsl = `
+ ${moduleScope}
+ @compute @workgroup_size(${t.params.workgroupSize})
+ fn main() {
+ ${functionScope}
+ ${checkZeroCode}
+ _ = zero;
+ }
+ `;
+
+ if (t.params.addressSpace === 'workgroup') {
+ // Populate the maximum amount of workgroup memory with known values to
+ // ensure initialization overrides in another shader.
+ const wg_memory_limits = t.device.limits.maxComputeWorkgroupStorageSize;
+ const wg_x_dim = t.device.limits.maxComputeWorkgroupSizeX;
+
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read> inputs : array<u32>;
+ @group(0) @binding(1) var<storage, read_write> outputs : array<u32>;
+ var<workgroup> wg_mem : array<u32, ${wg_memory_limits} / 4>;
+
+ @compute @workgroup_size(${wg_x_dim})
+ fn fill(@builtin(local_invocation_index) lid : u32) {
+ const num_u32_per_invocation = ${wg_memory_limits} / (4 * ${wg_x_dim});
+
+ for (var i = 0u; i < num_u32_per_invocation; i++) {
+ let idx = num_u32_per_invocation * lid + i;
+ wg_mem[idx] = inputs[idx];
+ }
+ workgroupBarrier();
+ // Copy out to avoid wg_mem being elided.
+ for (var i = 0u; i < num_u32_per_invocation; i++) {
+ let idx = num_u32_per_invocation * lid + i;
+ outputs[idx] = wg_mem[idx];
+ }
+ }
+ `;
+
+ const fillLayout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'read-only-storage' }
+ },
+ {
+ binding: 1,
+ visibility: GPUShaderStage.COMPUTE,
+ buffer: { type: 'storage' }
+ }]
+
+ });
+
+ const fillPipeline = await t.device.createComputePipelineAsync({
+ layout: t.device.createPipelineLayout({ bindGroupLayouts: [fillLayout] }),
+ label: 'Workgroup Fill Pipeline',
+ compute: {
+ module: t.device.createShaderModule({
+ code: wgsl
+ }),
+ entryPoint: 'fill'
+ }
+ });
+
+ const inputBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(wg_memory_limits / 4, (_i) => 0xdeadbeef)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(inputBuffer);
+ const outputBuffer = t.device.createBuffer({
+ size: wg_memory_limits,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const bg = t.device.createBindGroup({
+ layout: fillPipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: inputBuffer
+ }
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: outputBuffer
+ }
+ }]
+
+ });
+
+ const e = t.device.createCommandEncoder();
+ const p = e.beginComputePass();
+ p.setPipeline(fillPipeline);
+ p.setBindGroup(0, bg);
+ p.dispatchWorkgroups(1);
+ p.end();
+ t.queue.submit([e.finish()]);
+ }
+
+ const pipeline = await t.device.createComputePipelineAsync({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: wgsl
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const resultBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+ t.trackForCleanup(resultBuffer);
+
+ const zeroBuffer = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM
+ });
+ t.trackForCleanup(zeroBuffer);
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: resultBuffer
+ }
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: zeroBuffer
+ }
+ }]
+
+ });
+
+ 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(resultBuffer, new Uint32Array([0]));
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/types.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/types.js
new file mode 100644
index 0000000000..5810b8e9f1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/types.js
@@ -0,0 +1,289 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { keysOf } from '../../common/util/data_tables.js';import { assert } from '../../common/util/util.js';import { align } from '../util/math.js';
+
+const kArrayLength = 3;
+
+// never is the same as "must not"
+
+
+
+export const HostSharableTypes = ['i32', 'u32', 'f32'];
+
+/** Info for each plain scalar type. */
+export const kScalarTypeInfo =
+{
+ 'i32': { layout: { alignment: 4, size: 4 }, supportsAtomics: true, arrayLength: 1, innerLength: 0 },
+ 'u32': { layout: { alignment: 4, size: 4 }, supportsAtomics: true, arrayLength: 1, innerLength: 0 },
+ 'f32': { layout: { alignment: 4, size: 4 }, supportsAtomics: false, arrayLength: 1, innerLength: 0 },
+ 'bool': { layout: undefined, supportsAtomics: false, arrayLength: 1, innerLength: 0 }
+};
+/** List of all plain scalar types. */
+export const kScalarTypes = keysOf(kScalarTypeInfo);
+
+/** Info for each vecN<> container type. */
+export const kVectorContainerTypeInfo =
+{
+ '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 }
+};
+/** List of all vecN<> container types. */
+export const kVectorContainerTypes = keysOf(kVectorContainerTypeInfo);
+
+/** Info for each matNxN<> container type. */
+export const kMatrixContainerTypeInfo =
+{
+ '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 }
+};
+/** List of all matNxN<> container types. */
+export const kMatrixContainerTypes = keysOf(kMatrixContainerTypeInfo);
+
+
+
+
+
+export const kAccessModeInfo = {
+ read: { read: true, write: false },
+ write: { read: false, write: true },
+ read_write: { read: true, write: true }
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+export const kAddressSpaceInfo = {
+ storage: {
+ scope: 'module',
+ binding: true,
+ spell: 'must',
+ accessModes: ['read', 'read_write'],
+ spellAccessMode: 'may'
+ },
+ uniform: {
+ scope: 'module',
+ binding: true,
+ spell: 'must',
+ accessModes: ['read'],
+ spellAccessMode: 'never'
+ },
+ private: {
+ scope: 'module',
+ binding: false,
+ spell: 'must',
+ accessModes: ['read_write'],
+ spellAccessMode: 'never'
+ },
+ workgroup: {
+ scope: 'module',
+ binding: false,
+ spell: 'must',
+ accessModes: ['read_write'],
+ spellAccessMode: 'never'
+ },
+ function: {
+ scope: 'function',
+ binding: false,
+ spell: 'may',
+ accessModes: ['read_write'],
+ spellAccessMode: 'never'
+ },
+ handle: {
+ scope: 'module',
+ binding: true,
+ spell: 'never',
+ accessModes: [],
+ spellAccessMode: 'never'
+ }
+};
+
+/** List of texel formats and their shader representation */
+export const TexelFormats = [
+{ format: 'rgba8unorm', _shaderType: 'f32' },
+{ format: 'rgba8snorm', _shaderType: 'f32' },
+{ format: 'rgba8uint', _shaderType: 'u32' },
+{ format: 'rgba8sint', _shaderType: 'i32' },
+{ format: 'rgba16uint', _shaderType: 'u32' },
+{ format: 'rgba16sint', _shaderType: 'i32' },
+{ format: 'rgba16float', _shaderType: 'f32' },
+{ format: 'r32uint', _shaderType: 'u32' },
+{ format: 'r32sint', _shaderType: 'i32' },
+{ format: 'r32float', _shaderType: 'f32' },
+{ format: 'rg32uint', _shaderType: 'u32' },
+{ format: 'rg32sint', _shaderType: 'i32' },
+{ format: 'rg32float', _shaderType: 'f32' },
+{ format: 'rgba32uint', _shaderType: 'i32' },
+{ format: 'rgba32sint', _shaderType: 'i32' },
+{ format: 'rgba32float', _shaderType: 'f32' }];
+
+
+/**
+ * Generate a bunch types (vec, mat, sized/unsized array) for testing.
+ */
+export function* generateTypes({
+ addressSpace,
+ baseType,
+ containerType,
+ isAtomic = false
+
+
+
+
+
+
+
+
+}) {
+ const scalarInfo = kScalarTypeInfo[baseType];
+ if (isAtomic) {
+ assert(scalarInfo.supportsAtomics, 'type does not support atomics');
+ }
+ const scalarType = isAtomic ? `atomic<${baseType}>` : baseType;
+
+ // Storage and uniform require host-sharable types.
+ if (addressSpace === 'storage' || addressSpace === 'uniform') {
+ assert(isHostSharable(baseType), 'type ' + baseType.toString() + ' is not host sharable');
+ }
+
+ // Scalar types
+ if (containerType === 'scalar') {
+ yield {
+ type: `${scalarType}`,
+ _kTypeInfo: {
+ elementBaseType: `${scalarType}`,
+ ...scalarInfo
+ }
+ };
+ }
+
+ // Vector types
+ if (containerType === 'vector') {
+ for (const vectorType of kVectorContainerTypes) {
+ yield {
+ type: `${vectorType}<${scalarType}>`,
+ _kTypeInfo: { elementBaseType: baseType, ...kVectorContainerTypeInfo[vectorType] }
+ };
+ }
+ }
+
+ if (containerType === 'matrix') {
+ // Matrices can only be f32.
+ if (baseType === 'f32') {
+ for (const matrixType of kMatrixContainerTypes) {
+ const matrixInfo = kMatrixContainerTypeInfo[matrixType];
+ yield {
+ type: `${matrixType}<${scalarType}>`,
+ _kTypeInfo: {
+ elementBaseType: `vec${matrixInfo.innerLength}<${scalarType}>`,
+ ...matrixInfo
+ }
+ };
+ }
+ }
+ }
+
+ // Array types
+ if (containerType === 'array') {
+ 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
+ };
+
+ // Sized
+ if (addressSpace === 'uniform') {
+ yield {
+ type: `array<vec4<${scalarType}>,${kArrayLength}>`,
+ _kTypeInfo: arrayTypeInfo
+ };
+ } else {
+ yield { type: `array<${scalarType},${kArrayLength}>`, _kTypeInfo: arrayTypeInfo };
+ }
+ // Unsized
+ if (addressSpace === 'storage') {
+ yield { type: `array<${scalarType}>`, _kTypeInfo: arrayTypeInfo };
+ }
+ }
+
+ function arrayStride(elementLayout) {
+ return align(elementLayout.size, elementLayout.alignment);
+ }
+
+ function isHostSharable(baseType) {
+ for (const sharableType of HostSharableTypes) {
+ if (sharableType === baseType) return true;
+ }
+ return false;
+ }
+}
+
+/** Atomic access requires scalar/array container type and storage/workgroup memory. */
+export function supportsAtomics(p)
+
+
+
+
+{
+ return (
+ (p.addressSpace === 'storage' && p.storageMode === 'read_write' ||
+ p.addressSpace === 'workgroup') && (
+ p.containerType === 'scalar' || p.containerType === 'array'));
+
+}
+
+/** Generates an iterator of supported base types (i32/u32/f32/bool) */
+export function* supportedScalarTypes(p) {
+ for (const scalarType of kScalarTypes) {
+ const info = kScalarTypeInfo[scalarType];
+
+ // Test atomics only on supported scalar types.
+ if (p.isAtomic && !info.supportsAtomics) continue;
+
+ // Storage and uniform require host-sharable types.
+ const isHostShared = p.addressSpace === 'storage' || p.addressSpace === 'uniform';
+ if (isHostShared && info.layout === undefined) continue;
+
+ yield scalarType;
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/const_assert/const_assert.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/const_assert/const_assert.spec.js
new file mode 100644
index 0000000000..13e7e687f8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/const_assert/const_assert.spec.js
@@ -0,0 +1,201 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for const_assert`;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);
+
+/**
+ * Builds a const_assert() statement.
+ * @param expr the constant expression
+ * @param scope module-scope or function-scope constant expression
+ * @returns the WGSL code
+ */
+function buildStaticAssert(expr, scope) {
+ const stmt = `const_assert (${expr});`;
+ return scope === 'module' ? stmt : `fn f() { ${stmt} }`;
+}
+
+
+
+
+
+
+
+
+
+
+const kConditionCases = {
+ any_false: { expr: `any(vec3(false, false, false))`, val: false },
+ any_true: { expr: `any(vec3(false, true, false))`, val: true },
+ binary_op_eq_const_false: { expr: `one + 5 == two`, val: false },
+ binary_op_eq_const_true: { expr: `one + 1 == two`, val: true },
+ const_eq_literal_float_false: { expr: `one == 0.0`, val: false },
+ const_eq_literal_float_true: { expr: `one == 1.0`, val: true },
+ const_eq_literal_int_false: { expr: `one == 10`, val: false },
+ const_eq_literal_int_true: { expr: `one == 1`, val: true },
+ literal_false: { expr: `false`, val: false },
+ literal_not_false: { expr: `!false`, val: true },
+ literal_not_true: { expr: `!true`, val: false },
+ literal_true: { expr: `true`, val: true },
+ min_max_false: { expr: `min(three, max(two, one)) == 3`, val: false },
+ min_max_true: { expr: `min(three, max(two, one)) == 2`, val: true },
+ variable_false: { expr: `is_false`, val: false },
+ variable_not_false: { expr: `!is_false`, val: true },
+ variable_not_true: { expr: `!is_true`, val: false },
+ variable_true: { expr: `is_true`, val: true }
+};
+
+const kConditionConstants = `
+const one = 1;
+const two = 2;
+const three = 3;
+const is_true = true;
+const is_false = false;
+`;
+
+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']).
+beginSubcases()
+).
+fn((t) => {
+ const expr = kConditionCases[t.params.case].expr;
+ const val = kConditionCases[t.params.case].val;
+ t.expectCompileResult(
+ true,
+ kConditionConstants + buildStaticAssert(val ? expr : `!(${expr})`, t.params.scope)
+ );
+});
+
+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']).
+beginSubcases()
+).
+fn((t) => {
+ const expr = kConditionCases[t.params.case].expr;
+ const val = kConditionCases[t.params.case].val;
+ t.expectCompileResult(
+ false,
+ kConditionConstants + buildStaticAssert(val ? `!(${expr})` : expr, t.params.scope)
+ );
+});
+
+g.test('constant_expression_logical_or_no_assert').
+desc(
+ `Test that const_assert does not assert on a condition expression that contains a logical-or which evaluates to true`
+).
+params((u) =>
+u.
+combine('lhs', keysOf(kConditionCases)).
+combine('rhs', keysOf(kConditionCases)).
+combine('scope', ['module', 'function']).
+beginSubcases()
+).
+fn((t) => {
+ const expr = `(${kConditionCases[t.params.lhs].expr}) || (${
+ kConditionCases[t.params.rhs].expr
+ })`;
+ const val = kConditionCases[t.params.lhs].val || kConditionCases[t.params.rhs].val;
+ t.expectCompileResult(
+ true,
+ kConditionConstants + buildStaticAssert(val ? expr : `!(${expr})`, t.params.scope)
+ );
+});
+
+g.test('constant_expression_logical_or_assert').
+desc(
+ `Test that const_assert does assert on a condition expression that contains a logical-or which evaluates to false`
+).
+params((u) =>
+u.
+combine('lhs', keysOf(kConditionCases)).
+combine('rhs', keysOf(kConditionCases)).
+combine('scope', ['module', 'function']).
+beginSubcases()
+).
+fn((t) => {
+ const expr = `(${kConditionCases[t.params.lhs].expr}) || (${
+ kConditionCases[t.params.rhs].expr
+ })`;
+ const val = kConditionCases[t.params.lhs].val || kConditionCases[t.params.rhs].val;
+ t.expectCompileResult(
+ false,
+ kConditionConstants + buildStaticAssert(val ? `!(${expr})` : expr, t.params.scope)
+ );
+});
+
+g.test('constant_expression_logical_and_no_assert').
+desc(
+ `Test that const_assert does not assert on a condition expression that contains a logical-and which evaluates to true`
+).
+params((u) =>
+u.
+combine('lhs', keysOf(kConditionCases)).
+combine('rhs', keysOf(kConditionCases)).
+combine('scope', ['module', 'function']).
+beginSubcases()
+).
+fn((t) => {
+ const expr = `(${kConditionCases[t.params.lhs].expr}) && (${
+ kConditionCases[t.params.rhs].expr
+ })`;
+ const val = kConditionCases[t.params.lhs].val && kConditionCases[t.params.rhs].val;
+ t.expectCompileResult(
+ true,
+ kConditionConstants + buildStaticAssert(val ? expr : `!(${expr})`, t.params.scope)
+ );
+});
+
+g.test('constant_expression_logical_and_assert').
+desc(
+ `Test that const_assert does assert on a condition expression that contains a logical-and which evaluates to false`
+).
+params((u) =>
+u.
+combine('lhs', keysOf(kConditionCases)).
+combine('rhs', keysOf(kConditionCases)).
+combine('scope', ['module', 'function']).
+beginSubcases()
+).
+fn((t) => {
+ const expr = `(${kConditionCases[t.params.lhs].expr}) && (${
+ kConditionCases[t.params.rhs].expr
+ })`;
+ const val = kConditionCases[t.params.lhs].val && kConditionCases[t.params.rhs].val;
+ t.expectCompileResult(
+ false,
+ kConditionConstants + buildStaticAssert(val ? `!(${expr})` : expr, t.params.scope)
+ );
+});
+
+g.test('evaluation_stage').
+desc(`Test that the const_assert expression must be a constant expression.`).
+params((u) =>
+u.
+combine('scope', ['module', 'function']).
+combine('stage', ['constant', 'override', 'runtime']).
+beginSubcases()
+).
+fn((t) => {
+ const staticAssert = buildStaticAssert('value', t.params.scope);
+ switch (t.params.stage) {
+ case 'constant':
+ t.expectCompileResult(true, `const value = true;\n${staticAssert}`);
+ break;
+ case 'override':
+ t.expectCompileResult(false, `override value = true;\n${staticAssert}`);
+ break;
+ case 'runtime':
+ t.expectCompileResult(false, `var<private> value = true;\n${staticAssert}`);
+ break;
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/const.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/const.spec.js
new file mode 100644
index 0000000000..2ab34e65bf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/const.spec.js
@@ -0,0 +1,61 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for const declarations
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('no_direct_recursion').
+desc('Test that direct recursion of const declarations is rejected').
+params((u) => u.combine('target', ['a', 'b'])).
+fn((t) => {
+ const wgsl = `
+const a : i32 = 42;
+const b : i32 = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'a', wgsl);
+});
+
+g.test('no_indirect_recursion').
+desc('Test that indirect recursion of const declarations is rejected').
+params((u) => u.combine('target', ['a', 'b'])).
+fn((t) => {
+ const wgsl = `
+const a : i32 = 42;
+const b : i32 = c;
+const c : i32 = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'a', wgsl);
+});
+
+g.test('no_indirect_recursion_via_array_size').
+desc('Test that indirect recursion of const declarations via array size expressions is rejected').
+params((u) => u.combine('target', ['a', 'b'])).
+fn((t) => {
+ const wgsl = `
+const a = 4;
+const b = c[0];
+const c = array<i32, ${t.params.target}>(4, 4, 4, 4);
+`;
+ t.expectCompileResult(t.params.target === 'a', wgsl);
+});
+
+g.test('no_indirect_recursion_via_struct_attribute').
+desc('Test that indirect recursion of const declarations via struct members is rejected').
+params((u) =>
+u //
+.combine('target', ['a', 'b']).
+combine('attribute', ['align', 'location', 'size'])
+).
+fn((t) => {
+ const wgsl = `
+struct S {
+ @${t.params.attribute}(${t.params.target}) a : i32
+}
+const a = 4;
+const b = S(4).a;
+`;
+ t.expectCompileResult(t.params.target === 'a', wgsl);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/override.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/override.spec.js
new file mode 100644
index 0000000000..561527a622
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/override.spec.js
@@ -0,0 +1,31 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for override declarations
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('no_direct_recursion').
+desc('Test that direct recursion of override declarations is rejected').
+params((u) => u.combine('target', ['a', 'b'])).
+fn((t) => {
+ const wgsl = `
+override a : i32 = 42;
+override b : i32 = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'a', wgsl);
+});
+
+g.test('no_indirect_recursion').
+desc('Test that indirect recursion of override declarations is rejected').
+params((u) => u.combine('target', ['a', 'b'])).
+fn((t) => {
+ const wgsl = `
+override a : i32 = 42;
+override b : i32 = c;
+override c : i32 = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'a', wgsl);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/ptr_spelling.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/ptr_spelling.spec.js
new file mode 100644
index 0000000000..14e578b244
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/ptr_spelling.spec.js
@@ -0,0 +1,153 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validate spelling of pointer types.
+
+Pointer types may appear.
+
+They are parameterized by:
+- address space, always
+- store type
+- and access mode, as specified by the table in Address Spaces.
+ Concretely, only 'storage' address space allows it, and allows 'read', and 'read_write'.
+
+A pointer type can be spelled only if it corresponds to a variable that could be
+declared in the program. So we need to test combinations against possible variable
+declarations.
+`; // This file tests spelling of the pointer type on let-declared pointers.
+//
+// Spelling of pointer-typed parameters on user-declared functions is tested by
+// webgpu:shader,validation,functions,restrictions:function_parameter_types:"*"
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { kAccessModeInfo, kAddressSpaceInfo } from '../../types.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import {
+ pointerType,
+ explicitSpaceExpander,
+ accessModeExpander,
+ getVarDeclShader,
+ supportsWrite } from
+
+'./util.js';
+
+// Address spaces that can hold an i32 variable.
+const kNonHandleAddressSpaces = keysOf(kAddressSpaceInfo).filter(
+ (as) => as !== 'handle'
+);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('let_ptr_explicit_type_matches_var').
+desc(
+ 'Let-declared pointer with explicit type initialized from var with same address space and access mode'
+).
+specURL('https://w3.org/TR#ref-ptr-types').
+params((u) =>
+u // Generate non-handle variables in all valid permutations of address space and access mode.
+.combine('addressSpace', kNonHandleAddressSpaces).
+expand('explicitSpace', explicitSpaceExpander).
+combine('explicitAccess', [false, true]).
+expand('accessMode', accessModeExpander).
+combine('stage', ['compute']) // Only need to check compute shaders
+// Vary the store type.
+.combine('ptrStoreType', ['i32', 'u32'])
+).
+fn((t) => {
+ // Match the address space and access mode.
+ const prog = getVarDeclShader(t.params, `let p: ${pointerType(t.params)} = &x;`);
+ const ok = t.params.ptrStoreType === 'i32'; // The store type matches the variable's store type.
+
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('let_ptr_reads').
+desc('Validate reading via ptr when permitted by access mode').
+params((u) =>
+u // Generate non-handle variables in all valid permutations of address space and access mode.
+.combine('addressSpace', kNonHandleAddressSpaces).
+expand('explicitSpace', explicitSpaceExpander).
+combine('explicitAccess', [false, true]).
+expand('accessMode', accessModeExpander).
+combine('stage', ['compute']) // Only need to check compute shaders
+.combine('inferPtrType', [false, true]).
+combine('ptrStoreType', ['i32'])
+).
+fn((t) => {
+ // Try reading through the pointer.
+ const typePart = t.params.inferPtrType ? `: ${pointerType(t.params)}` : '';
+ const prog = getVarDeclShader(t.params, `let p${typePart} = &x; let read = *p;`);
+ const ok = true; // We can always read.
+
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('let_ptr_writes').
+desc('Validate writing via ptr when permitted by access mode').
+specURL('https://w3.org/TR#ref-ptr-types').
+params((u) =>
+u // Generate non-handle variables in all valid permutations of address space and access mode.
+.combine('addressSpace', kNonHandleAddressSpaces).
+expand('explicitSpace', explicitSpaceExpander).
+combine('explicitAccess', [false, true]).
+expand('accessMode', accessModeExpander).
+combine('stage', ['compute']) // Only need to check compute shaders
+.combine('inferPtrType', [false, true]).
+combine('ptrStoreType', ['i32'])
+).
+fn((t) => {
+ // Try writing through the pointer.
+ const typePart = t.params.inferPtrType ? `: ${pointerType(t.params)}` : '';
+ const prog = getVarDeclShader(t.params, `let p${typePart} = &x; *p = 42;`);
+ const ok = supportsWrite(t.params);
+
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('ptr_handle_space_invalid').fn((t) => {
+ t.expectCompileResult(false, 'alias p = ptr<handle,u32>;');
+});
+
+g.test('ptr_bad_store_type').
+params((u) => u.combine('storeType', ['undeclared', 'clamp', '1'])).
+fn((t) => {
+ t.expectCompileResult(false, `alias p = ptr<private,${t.params.storeType}>;`);
+});
+
+g.test('ptr_address_space_never_uses_access_mode').
+params((u) =>
+u.
+combine(
+ 'addressSpace',
+ keysOf(kAddressSpaceInfo).filter((i) => kAddressSpaceInfo[i].spellAccessMode === 'never')
+).
+combine('accessMode', keysOf(kAccessModeInfo))
+).
+fn((t) => {
+ const prog = `alias pty = ptr<${t.params.addressSpace},u32,;${t.params.accessMode}>;`;
+ t.expectCompileResult(false, prog);
+});
+
+const kStoreTypeNotInstantiable = {
+ ptr: 'alias p = ptr<storage,ptr<private,i32>>;',
+ privateAtomic: 'alias p = ptr<private,atomic<u32>>;',
+ functionAtomic: 'alias p = ptr<function,atomic<u32>>;',
+ uniformAtomic: 'alias p = ptr<uniform,atomic<u32>>;',
+ workgroupRTArray: 'alias p = ptr<workgroup,array<i32>>;',
+ uniformRTArray: 'alias p = ptr<uniform,array<i32>>;',
+ privateRTArray: 'alias p = ptr<private,array<i32>>;',
+ functionRTArray: 'alias p = ptr<function,array<i32>>;',
+ RTArrayNotLast: 'struct S { a: array<i32>, b: i32 } alias p = ptr<storage,S>;',
+ nestedRTArray: 'struct S { a: array<i32>, b: i32 } struct { s: S } alias p = ptr<storage,T>;'
+};
+
+g.test('ptr_not_instantiable').
+desc(
+ 'Validate that ptr type must correspond to a variable that could be declared somewhere; test bad cases'
+).
+params((u) => u.combine('case', keysOf(kStoreTypeNotInstantiable))).
+fn((t) => {
+ t.expectCompileResult(false, kStoreTypeNotInstantiable[t.params.case]);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/util.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/util.js
new file mode 100644
index 0000000000..23e083b71e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/util.js
@@ -0,0 +1,163 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import {
+
+ kAccessModeInfo,
+ kAddressSpaceInfo } from
+'../../types.js';
+
+/** An enumerator of shader stages */
+
+
+/** The list of all shader stages */
+export const kShaderStages = ['vertex', 'fragment', 'compute'];
+
+/**
+ * declareEntrypoint emits the WGSL to declare an entry point with the name, stage and body.
+ * The generated function will have an appropriate return type and return statement, so that `body`
+ * does not have to change between stage.
+ * @param arg - arg specifies the
+ * optional entry point function name, the shader stage, and the body of the
+ * function, excluding any automatically generated return statements.
+ * @returns the WGSL string for the entry point
+ */
+export function declareEntryPoint(arg)
+
+
+
+{
+ if (arg.name === undefined) {
+ arg.name = 'main';
+ }
+ switch (arg.stage) {
+ case 'vertex':
+ return `@vertex
+fn ${arg.name}() -> @builtin(position) vec4f {
+ ${arg.body}
+ return vec4f();
+}`;
+ case 'fragment':
+ return `@fragment
+fn ${arg.name}() {
+ ${arg.body}
+}`;
+ case 'compute':
+ return `@compute @workgroup_size(1)
+fn ${arg.name}() {
+ ${arg.body}
+}`;
+ }
+}
+
+/**
+ * @returns a WGSL var declaration with given parameters for variable 'x' and
+ * store type i32.
+ */
+export function declareVarX(addressSpace, accessMode) {
+ const parts = [];
+ if (addressSpace && kAddressSpaceInfo[addressSpace].binding) parts.push('@group(0) @binding(0) ');
+ parts.push('var');
+
+ const template_parts = [];
+ if (addressSpace) template_parts.push(addressSpace);
+ if (accessMode) template_parts.push(accessMode);
+ if (template_parts.length > 0) parts.push(`<${template_parts.join(',')}>`);
+
+ parts.push(' x: i32;');
+ return parts.join('');
+}
+
+/**
+ * @returns a list of booleans indicating valid cases of specifying the address
+ * space.
+ */
+export function explicitSpaceExpander(p) {
+ const info = kAddressSpaceInfo[p.addressSpace];
+ return info.spell === 'must' ? [true] : [true, false];
+}
+
+/**
+ * @returns a list of usable access modes under given experiment conditions, or undefined
+ * if none are allowed.
+ */
+export function accessModeExpander(p)
+
+
+{
+ const info = kAddressSpaceInfo[p.addressSpace];
+ return p.explicitAccess && info.spellAccessMode !== 'never' ? info.accessModes : [''];
+}
+
+/**
+ * @returns a WGSL program with a single variable declaration, with the
+ * given parameterization
+ */
+export function getVarDeclShader(
+p,
+
+
+
+
+
+
+additionalBody)
+{
+ const info = kAddressSpaceInfo[p.addressSpace];
+ const decl = declareVarX(
+ p.explicitSpace ? p.addressSpace : '',
+ p.explicitAccess ? p.accessMode : ''
+ );
+
+ additionalBody = additionalBody ?? '';
+
+ switch (info.scope) {
+ case 'module':
+ return decl + '\n' + declareEntryPoint({ stage: p.stage, body: additionalBody });
+
+ case 'function':
+ return declareEntryPoint({ stage: p.stage, body: decl + '\n' + additionalBody });
+ }
+}
+
+/**
+ * @returns the WGSL spelling of a pointer type corresponding to a variable
+ * declared with the given parameters.
+ */
+export function pointerType(p)
+
+
+
+
+{
+ const space = p.explicitSpace ? p.addressSpace : 'function';
+ const modePart = p.accessMode ? ',' + p.accessMode : '';
+ return `ptr<${space},${p.ptrStoreType}${modePart}>`;
+}
+
+/** @returns the effective access mode for the given experiment. */
+export function effectiveAccessMode(
+info,
+accessMode)
+{
+ return accessMode || info.accessModes[0]; // default is first.
+}
+
+/** @returns whether the setup allows reads */
+export function supportsRead(p)
+
+
+{
+ const info = kAddressSpaceInfo[p.addressSpace];
+ const mode = effectiveAccessMode(info, p.accessMode);
+ return info.accessModes.includes(mode) && kAccessModeInfo[mode].read;
+}
+
+/** @returns whether the setup allows writes */
+export function supportsWrite(p)
+
+
+{
+ const info = kAddressSpaceInfo[p.addressSpace];
+ const mode = effectiveAccessMode(info, p.accessMode);
+ return info.accessModes.includes(mode) && kAccessModeInfo[mode].write;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/var_access_mode.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/var_access_mode.spec.js
new file mode 100644
index 0000000000..466c9e32cd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/decl/var_access_mode.spec.js
@@ -0,0 +1,116 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+7.3 var Declarations
+
+The access mode always has a default value, and except for variables in the
+storage address space, must not be specified in the WGSL source. See §13.3 Address Spaces.
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { kAccessModeInfo, kAddressSpaceInfo } from '../../types.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import {
+ explicitSpaceExpander,
+ getVarDeclShader,
+ accessModeExpander,
+ supportsRead,
+ supportsWrite } from
+
+'./util.js';
+
+// Address spaces that can hold an i32 variable.
+const kNonHandleAddressSpaces = keysOf(kAddressSpaceInfo).filter(
+ (as) => as !== 'handle'
+);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('explicit_access_mode').
+desc('Validate uses of an explicit access mode on a var declaration').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#var-decls').
+params(
+ (u) =>
+ u.
+ combine('addressSpace', kNonHandleAddressSpaces).
+ combine('explicitSpace', [true, false])
+ // Only keep cases where:
+ // *if* the address space must be specified on a var decl (e.g. var<private>)
+ // then the address space will actually be specified in this test case.
+ .filter((t) => kAddressSpaceInfo[t.addressSpace].spell !== 'must' || t.explicitSpace).
+ combine('explicitAccess', [true]).
+ combine('accessMode', keysOf(kAccessModeInfo)).
+ combine('stage', ['compute']) // Only need to check compute shaders
+).
+fn((t) => {
+ const prog = getVarDeclShader(t.params);
+ const info = kAddressSpaceInfo[t.params.addressSpace];
+
+ const ok =
+ // The address space must be explicitly specified.
+ t.params.explicitSpace &&
+ // The address space must allow an access mode to be spelled, and the
+ // access mode must be in the list of modes for the address space.
+ info.spellAccessMode !== 'never' &&
+ info.accessModes.includes(t.params.accessMode);
+
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('implicit_access_mode').
+desc('Validate an implicit access mode on a var declaration').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#var-decls').
+params(
+ (u) =>
+ u.
+ combine('addressSpace', kNonHandleAddressSpaces).
+ expand('explicitSpace', explicitSpaceExpander).
+ combine('explicitAccess', [false]).
+ combine('accessMode', ['']).
+ combine('stage', ['compute']) // Only need to check compute shaders
+).
+fn((t) => {
+ const prog = getVarDeclShader(t.params);
+
+ // 7.3 var Declarations
+ // "The access mode always has a default value,.."
+ const ok = true;
+
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('read_access').
+desc('A variable can be read from when the access mode permits').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#var-decls').
+params(
+ (u) =>
+ u.
+ combine('addressSpace', kNonHandleAddressSpaces).
+ expand('explicitSpace', explicitSpaceExpander).
+ combine('explicitAccess', [false, true]).
+ expand('accessMode', accessModeExpander).
+ combine('stage', ['compute']) // Only need to check compute shaders
+).
+fn((t) => {
+ const prog = getVarDeclShader(t.params, 'let copy = x;');
+ const ok = supportsRead(t.params);
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('write_access').
+desc('A variable can be written to when the access mode permits').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#var-decls').
+params(
+ (u) =>
+ u.
+ combine('addressSpace', kNonHandleAddressSpaces).
+ expand('explicitSpace', explicitSpaceExpander).
+ combine('explicitAccess', [false, true]).
+ expand('accessMode', accessModeExpander).
+ combine('stage', ['compute']) // Only need to check compute shaders
+).
+fn((t) => {
+ const prog = getVarDeclShader(t.params, 'x = 0;');
+ const ok = supportsWrite(t.params);
+ t.expectCompileResult(ok, prog);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/access/vector.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/access/vector.spec.js
new file mode 100644
index 0000000000..7408ea2b04
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/access/vector.spec.js
@@ -0,0 +1,223 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for vector accesses
+`;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 kCases = {
+ // indexing with literal
+ literal_0: { wgsl: 'let r : T = v[0];', ok: true },
+ literal_1: { wgsl: 'let r : T = v[1];', ok: true },
+ literal_2: { wgsl: 'let r : T = v[2];', ok: (width) => width > 2 },
+ literal_3: { wgsl: 'let r : T = v[3];', ok: (width) => width > 3 },
+ literal_0i: { wgsl: 'let r : T = v[0i];', ok: true },
+ literal_1i: { wgsl: 'let r : T = v[1i];', ok: true },
+ literal_2i: { wgsl: 'let r : T = v[2i];', ok: (width) => width > 2 },
+ literal_3i: { wgsl: 'let r : T = v[3i];', ok: (width) => width > 3 },
+ literal_0u: { wgsl: 'let r : T = v[0u];', ok: true },
+ literal_1u: { wgsl: 'let r : T = v[1u];', ok: true },
+ literal_2u: { wgsl: 'let r : T = v[2u];', ok: (width) => width > 2 },
+ literal_3u: { wgsl: 'let r : T = v[3u];', ok: (width) => width > 3 },
+
+ // indexing with 'const' variable
+ const_0: { wgsl: 'const i = 0; let r : T = v[i];', ok: true },
+ const_1: { wgsl: 'const i = 1; let r : T = v[i];', ok: true },
+ const_2: { wgsl: 'const i = 2; let r : T = v[i];', ok: (width) => width > 2 },
+ const_3: { wgsl: 'const i = 3; let r : T = v[i];', ok: (width) => width > 3 },
+ const_0i: { wgsl: 'const i = 0i; let r : T = v[i];', ok: true },
+ const_1i: { wgsl: 'const i = 1i; let r : T = v[i];', ok: true },
+ const_2i: { wgsl: 'const i = 2i; let r : T = v[i];', ok: (width) => width > 2 },
+ const_3i: { wgsl: 'const i = 3i; let r : T = v[i];', ok: (width) => width > 3 },
+ const_0u: { wgsl: 'const i = 0u; let r : T = v[i];', ok: true },
+ const_1u: { wgsl: 'const i = 1u; let r : T = v[i];', ok: true },
+ const_2u: { wgsl: 'const i = 2u; let r : T = v[i];', ok: (width) => width > 2 },
+ const_3u: { wgsl: 'const i = 3u; let r : T = v[i];', ok: (width) => width > 3 },
+
+ // indexing with 'let' variable
+ let_0: { wgsl: 'let i = 0; let r : T = v[i];', ok: true },
+ let_1: { wgsl: 'let i = 1; let r : T = v[i];', ok: true },
+ let_2: { wgsl: 'let i = 2; let r : T = v[i];', ok: true },
+ let_3: { wgsl: 'let i = 3; let r : T = v[i];', ok: true },
+ let_0i: { wgsl: 'let i = 0i; let r : T = v[i];', ok: true },
+ let_1i: { wgsl: 'let i = 1i; let r : T = v[i];', ok: true },
+ let_2i: { wgsl: 'let i = 2i; let r : T = v[i];', ok: true },
+ let_3i: { wgsl: 'let i = 3i; let r : T = v[i];', ok: true },
+ let_0u: { wgsl: 'let i = 0u; let r : T = v[i];', ok: true },
+ let_1u: { wgsl: 'let i = 1u; let r : T = v[i];', ok: true },
+ let_2u: { wgsl: 'let i = 2u; let r : T = v[i];', ok: true },
+ let_3u: { wgsl: 'let i = 3u; let r : T = v[i];', ok: true },
+
+ // indexing with 'var' variable
+ var_0: { wgsl: 'var i = 0; let r : T = v[i];', ok: true },
+ var_1: { wgsl: 'var i = 1; let r : T = v[i];', ok: true },
+ var_2: { wgsl: 'var i = 2; let r : T = v[i];', ok: true },
+ var_3: { wgsl: 'var i = 3; let r : T = v[i];', ok: true },
+ var_0i: { wgsl: 'var i = 0i; let r : T = v[i];', ok: true },
+ var_1i: { wgsl: 'var i = 1i; let r : T = v[i];', ok: true },
+ var_2i: { wgsl: 'var i = 2i; let r : T = v[i];', ok: true },
+ var_3i: { wgsl: 'var i = 3i; let r : T = v[i];', ok: true },
+ var_0u: { wgsl: 'var i = 0u; let r : T = v[i];', ok: true },
+ var_1u: { wgsl: 'var i = 1u; let r : T = v[i];', ok: true },
+ var_2u: { wgsl: 'var i = 2u; let r : T = v[i];', ok: true },
+ var_3u: { wgsl: 'var i = 3u; let r : T = v[i];', ok: true },
+
+ // indexing with const expression
+ const_expr_0: { wgsl: 'let r : T = v[0 / 2];', ok: true },
+ const_expr_1: { wgsl: 'let r : T = v[2 / 2];', ok: true },
+ const_expr_2: { wgsl: 'let r : T = v[4 / 2];', ok: (width) => width > 2 },
+ const_expr_3: { wgsl: 'let r : T = v[6 / 2];', ok: (width) => width > 3 },
+ const_expr_2_via_trig: {
+ wgsl: 'let r : T = v[i32(tan(1.10714872) + 0.5)];',
+ ok: (width) => width > 2
+ },
+ const_expr_3_via_trig: {
+ wgsl: 'let r : T = v[u32(tan(1.24904577) + 0.5)];',
+ ok: (width) => width > 3
+ },
+ const_expr_2_via_vec2: {
+ wgsl: 'let r : T = v[vec2(3, 2)[1]];',
+ ok: (width) => width > 2
+ },
+ const_expr_3_via_vec2: {
+ wgsl: 'let r : T = v[vec2(3, 2).x];',
+ ok: (width) => width > 3
+ },
+ const_expr_2_via_vec2u: {
+ wgsl: 'let r : T = v[vec2u(3, 2)[1]];',
+ ok: (width) => width > 2
+ },
+ const_expr_3_via_vec2i: {
+ wgsl: 'let r : T = v[vec2i(3, 2).x];',
+ ok: (width) => width > 3
+ },
+ const_expr_2_via_array: {
+ wgsl: 'let r : T = v[array<i32, 2>(3, 2)[1]];',
+ ok: (width) => width > 2
+ },
+ const_expr_3_via_array: {
+ wgsl: 'let r : T = v[array<i32, 2>(3, 2)[0]];',
+ ok: (width) => width > 3
+ },
+ const_expr_2_via_struct: {
+ wgsl: 'let r : T = v[S(2).i];',
+ ok: (width) => width > 2
+ },
+ const_expr_3_via_struct: {
+ wgsl: 'let r : T = v[S(3).i];',
+ ok: (width) => width > 3
+ },
+
+ // single element convenience name accesses
+ x: { wgsl: 'let r : T = v.x;', ok: true },
+ y: { wgsl: 'let r : T = v.y;', ok: true },
+ z: { wgsl: 'let r : T = v.z;', ok: (width) => width > 2 },
+ w: { wgsl: 'let r : T = v.w;', ok: (width) => width > 3 },
+ r: { wgsl: 'let r : T = v.r;', ok: true },
+ g: { wgsl: 'let r : T = v.g;', ok: true },
+ b: { wgsl: 'let r : T = v.b;', ok: (width) => width > 2 },
+ a: { wgsl: 'let r : T = v.a;', ok: (width) => width > 3 },
+
+ // swizzles
+ xy: { wgsl: 'let r : vec2<T> = v.xy;', ok: true },
+ yx: { wgsl: 'let r : vec2<T> = v.yx;', ok: true },
+ xyx: { wgsl: 'let r : vec3<T> = v.xyx;', ok: true },
+ xyz: { wgsl: 'let r : vec3<T> = v.xyz;', ok: (width) => width > 2 },
+ zyx: { wgsl: 'let r : vec3<T> = v.zyx;', ok: (width) => width > 2 },
+ xyxy: { wgsl: 'let r : vec4<T> = v.xyxy;', ok: true },
+ xyxz: { wgsl: 'let r : vec4<T> = v.xyxz;', ok: (width) => width > 2 },
+ xyzw: { wgsl: 'let r : vec4<T> = v.xyzw;', ok: (width) => width > 3 },
+ yxwz: { wgsl: 'let r : vec4<T> = v.yxwz;', ok: (width) => width > 3 },
+ rg: { wgsl: 'let r : vec2<T> = v.rg;', ok: true },
+ gr: { wgsl: 'let r : vec2<T> = v.gr;', ok: true },
+ rgg: { wgsl: 'let r : vec3<T> = v.rgg;', ok: true },
+ rgb: { wgsl: 'let r : vec3<T> = v.rgb;', ok: (width) => width > 2 },
+ grb: { wgsl: 'let r : vec3<T> = v.grb;', ok: (width) => width > 2 },
+ rgbr: { wgsl: 'let r : vec4<T> = v.rgbr;', ok: (width) => width > 2 },
+ rgba: { wgsl: 'let r : vec4<T> = v.rgba;', ok: (width) => width > 3 },
+ gbra: { wgsl: 'let r : vec4<T> = v.gbra;', ok: (width) => width > 3 },
+
+ // swizzle chains
+ xy_yx: { wgsl: 'let r : vec2<T> = v.xy.yx;', ok: true },
+ xyx_xxy: { wgsl: 'let r : vec3<T> = v.xyx.xxy;', ok: true },
+ xyz_zyx: { wgsl: 'let r : vec3<T> = v.xyz.zyx;', ok: (width) => width > 2 },
+ xyxy_rrgg: { wgsl: 'let r : vec4<T> = v.xyxy.rrgg;', ok: true },
+ rbrg_xyzw: { wgsl: 'let r : vec4<T> = v.rbrg.xyzw;', ok: (width) => width > 2 },
+ xyxz_rbg_yx: { wgsl: 'let r : vec2<T> = v.xyxz.rbg.yx;', ok: (width) => width > 2 },
+ wxyz_bga_xy: { wgsl: 'let r : vec2<T> = v.wxyz.bga.xy;', ok: (width) => width > 3 },
+
+ // error: invalid convenience letterings
+ xq: { wgsl: 'let r : vec2<T> = v.xq;', ok: false },
+ py: { wgsl: 'let r : vec2<T> = v.py;', ok: false },
+
+ // error: mixed convenience letterings
+ xg: { wgsl: 'let r : vec2<T> = v.xg;', ok: false },
+ ryb: { wgsl: 'let r : vec3<T> = v.ryb;', ok: false },
+ xgza: { wgsl: 'let r : vec4<T> = v.xgza;', ok: false },
+
+ // error: too many swizzle elements
+ xxxxx: { wgsl: 'let r = v.xxxxx;', ok: false },
+ rrrrr: { wgsl: 'let r = v.rrrrr;', ok: false },
+ yxwxy: { wgsl: 'let r = v.yxwxy;', ok: false },
+ rgbar: { wgsl: 'let r = v.rgbar;', ok: false },
+
+ // error: invalid index value
+ literal_5: { wgsl: 'let r : T = v[5];', ok: false },
+ literal_minus_1: { wgsl: 'let r : T = v[-1];', ok: false },
+
+ // error: invalid index type
+ float_idx: { wgsl: 'let r : T = v[1.0];', ok: false },
+ bool_idx: { wgsl: 'let r : T = v[true];', ok: false },
+ array_idx: { wgsl: 'let r : T = v[array<i32, 2>()];', ok: false }
+};
+
+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']).
+combine('vector_width', [2, 3, 4]).
+combine('element_type', ['i32', 'u32', 'f32', 'f16', 'bool'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.element_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const c = kCases[t.params.case];
+ const enables = t.params.element_type === 'f16' ? 'enable f16;' : '';
+ const prefix = `${enables}
+
+alias T = ${t.params.element_type};
+
+struct S {
+ i : i32,
+}
+
+@compute @workgroup_size(1)
+`;
+ const code =
+ t.params.vector_decl === 'param' ?
+ `${prefix}
+fn main() {
+ F(vec${t.params.vector_width}<T>());
+}
+
+fn F(v : vec${t.params.vector_width}<T>) {
+ ${c.wgsl}
+}
+` :
+ `${prefix}
+fn main() {
+ ${t.params.vector_decl} v = vec${t.params.vector_width}<T>();
+ ${c.wgsl}
+}
+`;
+ const pass = typeof c.ok === 'function' ? c.ok(t.params.vector_width) : c.ok;
+ t.expectCompileResult(pass, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/binary/bitwise_shift.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/binary/bitwise_shift.spec.js
new file mode 100644
index 0000000000..ff3e1ae525
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/binary/bitwise_shift.spec.js
@@ -0,0 +1,166 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for the bitwise shift binary expression operations
+`;import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// Converts v to signed decimal number.
+// Required because JS binary literals are always interpreted as unsigned numbers.
+function signed(v) {
+ return new Int32Array([v])[0];
+}
+
+// Return vector form of size `size` of input value `v`, or `v` if size is undefined.
+function vectorize(v, size) {
+ if (size !== undefined) {
+ return `vec${size}(${v})`;
+ }
+ return v;
+}
+
+const kLeftShiftCases = [
+// rhs >= bitwidth fails
+{ lhs: `0u`, rhs: `31u`, pass: true },
+{ lhs: `0u`, rhs: `32u`, pass: false },
+{ lhs: `0u`, rhs: `33u`, pass: false },
+{ lhs: `0u`, rhs: `1000u`, pass: false },
+{ lhs: `0u`, rhs: `0xFFFFFFFFu`, pass: false },
+
+{ lhs: `0i`, rhs: `31u`, pass: true },
+{ lhs: `0i`, rhs: `32u`, pass: false },
+{ lhs: `0i`, rhs: `33u`, pass: false },
+{ lhs: `0i`, rhs: `1000u`, pass: false },
+{ lhs: `0i`, rhs: `0xFFFFFFFFu`, pass: false },
+
+// Signed overflow (sign change)
+{ lhs: `${0b01000000000000000000000000000000}i`, rhs: `1u`, pass: false },
+{ lhs: `${0b01111111111111111111111111111111}i`, rhs: `1u`, pass: false },
+{ lhs: `${0b00000000000000000000000000000001}i`, rhs: `31u`, pass: false },
+// Same cases should pass if lhs is unsigned
+{ lhs: `${0b01000000000000000000000000000000}u`, rhs: `1u`, pass: true },
+{ lhs: `${0b01111111111111111111111111111111}u`, rhs: `1u`, pass: true },
+{ lhs: `${0b00000000000000000000000000000001}u`, rhs: `31u`, pass: true },
+
+// Unsigned overflow
+{ lhs: `${0b11000000000000000000000000000000}u`, rhs: `1u`, pass: false },
+{ lhs: `${0b11111111111111111111111111111111}u`, rhs: `1u`, pass: false },
+{ lhs: `${0b11111111111111111111111111111111}u`, rhs: `31u`, pass: false },
+// Same cases should pass if lhs is signed
+{ lhs: `${signed(0b11000000000000000000000000000000)}i`, rhs: `1u`, pass: true },
+{ lhs: `${signed(0b11111111111111111111111111111111)}i`, rhs: `1u`, pass: true },
+{ lhs: `${signed(0b11111111111111111111111111111111)}i`, rhs: `31u`, pass: true },
+
+// Shift by negative is an error
+{ lhs: `1`, rhs: `-1`, pass: false },
+{ lhs: `1i`, rhs: `-1`, pass: false },
+{ lhs: `1u`, rhs: `-1`, pass: false }];
+
+
+g.test('shift_left_concrete').
+desc('Tests validation of binary left shift of concrete values').
+params((u) =>
+u.
+combine('case', kLeftShiftCases) //
+.combine('vectorize', [undefined, 2, 3, 4])
+).
+fn((t) => {
+ const lhs = t.params.case.lhs;
+ const rhs = t.params.case.rhs;
+ const vec_size = t.params.vectorize;
+
+ const code = `
+@compute @workgroup_size(1)
+fn main() {
+ const r = ${vectorize(lhs, vec_size)} << ${vectorize(rhs, vec_size)};
+}
+ `;
+ 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 },
+{ lhs: `0u`, rhs: `32u`, pass: false },
+{ lhs: `0u`, rhs: `33u`, pass: false },
+{ lhs: `0u`, rhs: `1000u`, pass: false },
+{ lhs: `0u`, rhs: `0xFFFFFFFFu`, pass: false },
+
+{ lhs: `0i`, rhs: `31u`, pass: true },
+{ lhs: `0i`, rhs: `32u`, pass: false },
+{ lhs: `0i`, rhs: `33u`, pass: false },
+{ lhs: `0i`, rhs: `1000u`, pass: false },
+{ lhs: `0i`, rhs: `0xFFFFFFFFu`, pass: false },
+
+// Shift by negative is an error
+{ lhs: `1`, rhs: `-1`, pass: false },
+{ lhs: `1i`, rhs: `-1`, pass: false },
+{ lhs: `1u`, rhs: `-1`, pass: false }];
+
+
+g.test('shift_right_concrete').
+desc('Tests validation of binary right shift of concrete values').
+params((u) =>
+u.
+combine('case', kRightShiftCases) //
+.combine('vectorize', [undefined, 2, 3, 4])
+).
+fn((t) => {
+ const lhs = t.params.case.lhs;
+ const rhs = t.params.case.rhs;
+ const vec_size = t.params.vectorize;
+
+ const code = `
+@compute @workgroup_size(1)
+fn main() {
+ const r = ${vectorize(lhs, vec_size)} >> ${vectorize(rhs, vec_size)};
+}
+ `;
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/abs.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/abs.spec.js
new file mode 100644
index 0000000000..255c8ab2e2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/abs.spec.js
@@ -0,0 +1,54 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'abs';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 {
+ TypeF16,
+ elementType,
+ kAllFloatAndIntegerScalarsAndVectors } 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(kAllFloatAndIntegerScalarsAndVectors);
+
+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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // abs() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acos.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acos.spec.js
new file mode 100644
index 0000000000..d7caeca828
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acos.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'acos';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinusTwoToTwo,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = Math.abs(t.params.value) <= 1;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acosh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acosh.spec.js
new file mode 100644
index 0000000000..037d058fb6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/acosh.spec.js
@@ -0,0 +1,80 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'acosh';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinusTwoToTwo,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(fullRangeForType(kValuesTypes[u.type]), kMinusTwoToTwo))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(Math.acosh(t.params.value), elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asin.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asin.spec.js
new file mode 100644
index 0000000000..ec5c51fe15
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asin.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'asin';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinusTwoToTwo,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = Math.abs(t.params.value) <= 1;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asinh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asinh.spec.js
new file mode 100644
index 0000000000..ccc73de0fc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/asinh.spec.js
@@ -0,0 +1,82 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'asinh';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) =>
+unique(fullRangeForType(kValuesTypes[u.type]), linearRange(-2000, 2000, 10))
+)
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(Math.asinh(t.params.value), elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan.spec.js
new file mode 100644
index 0000000000..2cd971e678
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan.spec.js
@@ -0,0 +1,79 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'atan';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinus3PiTo3Pi,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = true;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan2.spec.js
new file mode 100644
index 0000000000..e0fded50b1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atan2.spec.js
@@ -0,0 +1,106 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'atan2';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 {
+ TypeF16,
+ TypeF32,
+ Vector,
+ VectorType,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kSparseMinus3PiTo3Pi,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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('y', (u) => unique(kSparseMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type], 4))).
+expand('x', (u) => unique(kSparseMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type], 4)))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ 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)
+ );
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.y), type.create(t.params.x)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+g.test('integer_argument_y').
+desc(
+ `
+Validates that scalar and vector integer arguments are rejected by ${builtin}()
+`
+).
+params((u) => u.combine('type', keysOf(kIntegerArgumentTypes))).
+fn((t) => {
+ const yTy = kIntegerArgumentTypes[t.params.type];
+ const xTy = yTy instanceof Vector ? new VectorType(yTy.size, TypeF32) : TypeF32;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */yTy === TypeF32,
+ [yTy.create(1), xTy.create(1)],
+ 'constant'
+ );
+});
+
+g.test('integer_argument_x').
+desc(
+ `
+Validates that scalar and vector integer arguments are rejected by ${builtin}()
+`
+).
+params((u) => u.combine('type', keysOf(kIntegerArgumentTypes))).
+fn((t) => {
+ const xTy = kIntegerArgumentTypes[t.params.type];
+ const yTy = xTy instanceof Vector ? new VectorType(xTy.size, TypeF32) : TypeF32;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */xTy === TypeF32,
+ [yTy.create(1), xTy.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atanh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atanh.spec.js
new file mode 100644
index 0000000000..e252551219
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atanh.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'atanh';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinusTwoToTwo,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = Math.abs(t.params.value) < 1;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atomics.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atomics.spec.js
new file mode 100644
index 0000000000..3d51650c95
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/atomics.spec.js
@@ -0,0 +1,70 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for atomic 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 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)' }
+};
+
+g.test('stage').
+specURL('https://www.w3.org/TR/WGSL/#atomic-rmw').
+desc(
+ `
+Atomic built-in functions must not be used in a vertex shader stage.
+`
+).
+params((u) =>
+u.
+combine('stage', ['fragment', 'vertex', 'compute']) //
+.combine('atomicOp', keysOf(kAtomicOps))
+).
+fn((t) => {
+ const atomicOp = kAtomicOps[t.params.atomicOp].src;
+ let code = `
+@group(0) @binding(0) var<storage, read_write> a: atomic<i32>;
+`;
+
+ switch (t.params.stage) {
+ case 'compute':
+ code += `
+@compute @workgroup_size(1,1,1) fn main() {
+ ${atomicOp};
+}`;
+ break;
+
+ case 'fragment':
+ code += `
+@fragment fn main() -> @location(0) vec4<f32> {
+ ${atomicOp};
+ return vec4<f32>();
+}`;
+ break;
+
+ case 'vertex':
+ code += `
+@vertex fn vmain() -> @builtin(position) vec4<f32> {
+ ${atomicOp};
+ return vec4<f32>();
+}`;
+ break;
+ }
+
+ const pass = t.params.stage !== 'vertex';
+ t.expectCompileResult(pass, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/bitcast.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/bitcast.spec.js
new file mode 100644
index 0000000000..71ef456929
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/bitcast.spec.js
@@ -0,0 +1,393 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation negative tests for bitcast builtins.
+`;import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { kBit } from '../../../../../util/constants.js';
+import { linearRange } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A VectorCase specifies the number of components a vector type has,
+// and which component will have a bad value.
+// Use width = 1 to indicate a scalar.
+
+const kVectorCases = {
+ v1_b0: { width: 1, badIndex: 0 },
+ v2_b0: { width: 2, badIndex: 0 },
+ v2_b1: { width: 2, badIndex: 1 },
+ v3_b0: { width: 3, badIndex: 0 },
+ v3_b1: { width: 3, badIndex: 1 },
+ v3_b2: { width: 3, badIndex: 2 },
+ v4_b0: { width: 4, badIndex: 0 },
+ v4_b1: { width: 4, badIndex: 1 },
+ v4_b2: { width: 4, badIndex: 2 },
+ v4_b3: { width: 4, badIndex: 3 }
+};
+
+const numNaNs = 4;
+const f32InfAndNaNInU32 = [
+// 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];
+
+
+g.test('bad_const_to_f32').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+It is a shader-creation error if any const-expression of floating-point type evaluates to NaN or infinity.
+`
+).
+params((u) =>
+u.
+combine('fromScalarType', ['i32', 'u32']).
+combine('vectorize', keysOf(kVectorCases)).
+beginSubcases()
+// Also validate that testcases without using bad bit can pass the exam
+.combine('useBadValue', [true, false]).
+expand('bitBadValue', (p) =>
+p.useBadValue ? [...f32InfAndNaNInU32] : [0]
+)
+).
+fn((t) => {
+ // For scalar cases, generate code like:
+ // const f = bitcast<f32>(i32(u32(0x7f800000)));
+ // For vector cases, generate code where one component is bad. In this case
+ // width=4 and badIndex=2
+ // const f = bitcast<vec4f>(vec4<32>(0,0,i32(u32(0x7f800000)),0));
+ const vectorize = kVectorCases[t.params.vectorize];
+ const width = vectorize.width;
+ const badIndex = vectorize.badIndex;
+ const badScalar = `${t.params.fromScalarType}(u32(${t.params.bitBadValue}))`;
+ const destType = width === 1 ? 'f32' : `vec${width}f`;
+ const srcType =
+ width === 1 ? t.params.fromScalarType : `vec${width}<${t.params.fromScalarType}>`;
+ const components = [...Array(width).keys()].
+ map((i) => i === badIndex ? badScalar : '0').
+ join(',');
+ const code = `const f = bitcast<${destType}>(${srcType}(${components}));`;
+ t.expectCompileResult(!t.params.useBadValue, code);
+});
+
+const f16InfAndNaNInU16 = [
+// 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 i16.max = 32767
+...linearRange(kBit.f16.positive.infinity + 1, 32767, 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 u16.max = 65535
+...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs),
+kBit.f16.positive.infinity,
+kBit.f16.negative.infinity];
+
+
+/**
+ * @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) {
+ 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);
+}
+
+g.test('bad_const_to_f16').
+specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation').
+desc(
+ `
+It is a shader-creation error if any const-expression of floating-point type evaluates to NaN or infinity.
+`
+).
+params((u) =>
+u.
+combine('fromScalarType', ['i32', 'u32']).
+combine('vectorize', keysOf(kVectorCases))
+// Only test valid bitcast to vec2<f16> or vec4<f16>
+.filter((p) => kVectorCases[p.vectorize].width % 2 === 0).
+beginSubcases()
+// Also validate that testcases without using bad bit can pass the exam
+.combine('useBadValue', [true, false]).
+expand('bitBadValue', (p) =>
+p.useBadValue ? [...f16InfAndNaNInU16] : [0]
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ // For width = 2 generate code like:
+ // const f = bitcast<vec2<f16>>(i32(u32(0x7f800000)));
+ // And for width = 4:
+ // const f = bitcast<vec4<f16>>(vec2<i32>(0,i32(u32(0x7f800000))));
+ const vectorize = kVectorCases[t.params.vectorize];
+ const width = vectorize.width;
+ const badIndex = vectorize.badIndex;
+
+ // Only bistcast to vec2<f16> or vec4<f16> is valid.
+ assert(width === 2 || width === 4);
+
+ // Put the bad f16 bits into lower 16 bits of source element if bad index is 0 or 2, else higher 16 bits.
+ const badSrcElemBitsInU32 = u16x2ToU32(
+ badIndex % 2 === 0 ? [t.params.bitBadValue, 0] : [0, t.params.bitBadValue]
+ );
+ const badScalar = `${t.params.fromScalarType}(u32(${badSrcElemBitsInU32}))`;
+
+ const destType = `vec${width}<f16>`;
+ const srcType = width === 2 ? t.params.fromScalarType : `vec2<${t.params.fromScalarType}>`;
+ const components = [...Array(width / 2).keys()].
+ map((i) => i === badIndex >> 1 ? badScalar : '0').
+ join(',');
+ const code = `
+ enable f16;
+ const f = bitcast<${destType}>(${srcType}(${components}));`;
+ t.expectCompileResult(!t.params.useBadValue, code);
+});
+
+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), []);
+const bool_types = ['bool', ...[2, 3, 4].map((i) => `vec${i}<bool>`)];
+
+g.test('bad_type_constructible').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(
+ `
+Bitcast only applies to concrete numeric scalar or concrete numeric vector.
+Test constructible types.
+`
+).
+params((u) =>
+u.
+combine('type', [
+...f32_matrix_types,
+...f16_matrix_types,
+...bool_types,
+'array<i32,2>',
+'S']
+).
+combine('direction', ['to', 'from'])
+).
+beforeAllSubcases((t) => {
+ if (t.params.type.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const T = t.params.type;
+ const enable_directives = t.params.type.includes('f16') ? 'enable f16;\n' : '';
+ const preamble = T === 'S' ? 'struct S { a:i32 } ' : '';
+ // Create a value of type T using zero-construction: T().
+ const srcVal = t.params.direction === 'to' ? '0' : `${T}()`;
+ const destType = t.params.direction === 'to' ? T : 'i32';
+ const code = enable_directives + preamble + `const x = bitcast<${destType}>(${srcVal});`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('bad_type_nonconstructible').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(
+ `
+Bitcast only applies to concrete numeric scalar or concrete numeric vector.
+Test non-constructible types.
+`
+).
+params((u) => u.combine('var', ['s', 't', 'b', 'p']).combine('direction', ['to', 'from'])).
+fn((t) => {
+ const typeOf = {
+ s: 'sampler',
+ t: 'texture_depth_2d',
+ b: 'array<i32>',
+ p: 'ptr<private,i32>'
+ };
+ const srcVal = t.params.direction === 'to' ? '0' : t.params.var;
+ const destType = t.params.direction === 'to' ? typeOf[t.params.var] : 'i32';
+ const code = `
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_depth_2d;
+ @group(0) @binding(2) var<storage> b: array<i32>;
+ var<private> v: i32;
+ @compute @workgroup_size(1)
+ fn main() {
+ let p = &v;
+ let x = bitcast<${destType}>(${srcVal});
+ }
+ `;
+ t.expectCompileResult(false, code);
+});
+
+g.test('bad_to_vec3h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(
+ `
+Can't cast numeric type to vec3<f16> because it is 48 bits wide
+and no other type is that size.
+`
+).
+params((u) =>
+u.
+combine('other_type', [
+'bool',
+'u32',
+'i32',
+'f32',
+'vec2<bool>',
+'vec3<bool>',
+'vec4<bool>',
+'vec2u',
+'vec3u',
+'vec4u',
+'vec2i',
+'vec3i',
+'vec4i',
+'vec2f',
+'vec3f',
+'vec4f',
+'vec2h',
+'vec4h']
+).
+combine('direction', ['to', 'from']).
+combine('type', ['vec3<f16>', 'vec3h'])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const src_type = t.params.direction === 'to' ? t.params.type : t.params.other_type;
+ const dst_type = t.params.direction === 'from' ? t.params.type : t.params.other_type;
+ const code = `
+enable f16;
+@fragment
+fn main() {
+ var src : ${src_type};
+ let dst = bitcast<${dst_type}>(src);
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('bad_to_f16').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(
+ `
+Can't cast non-16-bit types to f16 because it is 16 bits wide
+and no other type is that size.
+`
+).
+params((u) =>
+u.
+combine('other_type', [
+'bool',
+'u32',
+'i32',
+'f32',
+'vec2<bool>',
+'vec3<bool>',
+'vec4<bool>',
+'vec2u',
+'vec3u',
+'vec4u',
+'vec2i',
+'vec3i',
+'vec4i',
+'vec2f',
+'vec3f',
+'vec4f',
+'vec2h',
+'vec3h',
+'vec4h']
+).
+combine('direction', ['to', 'from'])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const src_type = t.params.direction === 'to' ? 'f16' : t.params.other_type;
+ const dst_type = t.params.direction === 'from' ? 'f16' : t.params.other_type;
+ const code = `
+enable f16;
+@fragment
+fn main() {
+ var src : ${src_type};
+ let dst = bitcast<${dst_type}>(src);
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('valid_vec2h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`Check valid vec2<f16> bitcasts`).
+params((u) =>
+u.
+combine('other_type', ['u32', 'i32', 'f32']).
+combine('type', ['vec2<f16>', 'vec2h']).
+combine('direction', ['to', 'from'])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const src_type = t.params.direction === 'to' ? t.params.type : t.params.other_type;
+ const dst_type = t.params.direction === 'from' ? t.params.type : t.params.other_type;
+ const code = `
+enable f16;
+@fragment
+fn main() {
+ var src : ${src_type};
+ let dst = bitcast<${dst_type}>(src);
+}`;
+ t.expectCompileResult(true, code);
+});
+
+g.test('valid_vec4h').
+specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin').
+desc(`Check valid vec2<f16> bitcasts`).
+params((u) =>
+u.
+combine('other_type', [
+'vec2<u32>',
+'vec2u',
+'vec2<i32>',
+'vec2i',
+'vec2<f32>',
+'vec2f']
+).
+combine('type', ['vec4<f16>', 'vec4h']).
+combine('direction', ['to', 'from'])
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const src_type = t.params.direction === 'to' ? t.params.type : t.params.other_type;
+ const dst_type = t.params.direction === 'from' ? t.params.type : t.params.other_type;
+ const code = `
+enable f16;
+@fragment
+fn main() {
+ var src : ${src_type};
+ let dst = bitcast<${dst_type}>(src);
+}`;
+ t.expectCompileResult(true, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/ceil.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/ceil.spec.js
new file mode 100644
index 0000000000..9d6fb1e30c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/ceil.spec.js
@@ -0,0 +1,75 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'ceil';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+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])).
+expand('value', (u) => fullRangeForType(kValuesTypes[u.type]))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // ceil() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/clamp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/clamp.spec.js
new file mode 100644
index 0000000000..ae83f29898
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/clamp.spec.js
@@ -0,0 +1,57 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'clamp';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 {
+ TypeF16,
+ elementType,
+ kAllFloatAndIntegerScalarsAndVectors } 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(kAllFloatAndIntegerScalarsAndVectors);
+
+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('e', (u) => fullRangeForType(kValuesTypes[u.type], 3)).
+expand('low', (u) => fullRangeForType(kValuesTypes[u.type], 4)).
+expand('high', (u) => fullRangeForType(kValuesTypes[u.type], 4))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = t.params.low <= t.params.high;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.e), type.create(t.params.low), type.create(t.params.high)],
+ t.params.stage
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/const_override_validation.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/const_override_validation.js
new file mode 100644
index 0000000000..47431b3a02
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/const_override_validation.js
@@ -0,0 +1,202 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../../../../common/util/util.js';import { kValue } from '../../../../../util/constants.js';import {
+
+ TypeF16,
+
+ elementType,
+ elementsOf,
+ isAbstractType } from
+'../../../../../util/conversion.js';
+import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.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];
+
+
+/// 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];
+
+
+/// The evaluation stages to test
+export const kConstantAndOverrideStages = ['constant', 'override'];
+
+
+
+/**
+ * @returns true if evaluation stage `stage` supports expressions of type @p.
+ */
+export function stageSupportsType(stage, type) {
+ if (stage === 'override' && isAbstractType(elementType(type))) {
+ // Abstract numerics are concretized before being used in an override expression.
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Runs a validation test to check that evaluation of `builtin` either evaluates with or without
+ * error at shader creation time or pipeline creation time.
+ * @param t the ShaderValidationTest
+ * @param builtin the name of the builtin
+ * @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
+ */
+export function validateConstOrOverrideBuiltinEval(
+t,
+builtin,
+expectedResult,
+args,
+stage)
+{
+ const elTys = args.map((arg) => elementType(arg.type));
+ const enables = elTys.some((ty) => ty === TypeF16) ? 'enable f16;' : '';
+
+ switch (stage) {
+ case 'constant':{
+ t.expectCompileResult(
+ expectedResult,
+ `${enables}
+const v = ${builtin}(${args.map((arg) => arg.wgsl()).join(', ')});`
+ );
+ break;
+ }
+ case 'override':{
+ assert(!elTys.some((ty) => isAbstractType(ty)));
+ const constants = {};
+ const overrideDecls = [];
+ const callArgs = [];
+ let numOverrides = 0;
+ for (const arg of args) {
+ const argOverrides = [];
+ for (const el of elementsOf(arg)) {
+ const name = `o${numOverrides++}`;
+ overrideDecls.push(`override ${name} : ${el.type};`);
+ argOverrides.push(name);
+ constants[name] = Number(el.value);
+ }
+ callArgs.push(`${arg.type}(${argOverrides.join(', ')})`);
+ }
+ t.expectPipelineResult({
+ expectedResult,
+ code: `${enables}
+${overrideDecls.join('\n')}
+var<private> v = ${builtin}(${callArgs.join(', ')});`,
+ constants,
+ reference: ['v']
+ });
+ break;
+ }
+ }
+}
+
+/** @returns a sweep of the representable values for element type of `type` */
+export function fullRangeForType(type, count) {
+ if (count === undefined) {
+ count = 25;
+ }
+ switch (elementType(type)?.kind) {
+ case 'abstract-float':
+ return fullF64Range({
+ pos_sub: Math.ceil(count * 1 / 5),
+ pos_norm: Math.ceil(count * 4 / 5)
+ });
+ case 'f32':
+ return fullF32Range({
+ pos_sub: Math.ceil(count * 1 / 5),
+ pos_norm: Math.ceil(count * 4 / 5)
+ });
+ case 'f16':
+ return fullF16Range({
+ pos_sub: Math.ceil(count * 1 / 5),
+ pos_norm: Math.ceil(count * 4 / 5)
+ });
+ case 'i32':
+ return linearRange(kValue.i32.negative.min, kValue.i32.positive.max, count).map((f) =>
+ Math.floor(f)
+ );
+ case 'u32':
+ return linearRange(0, kValue.u32.max, count).map((f) => Math.floor(f));
+ }
+ unreachable();
+}
+
+/** @returns all the values in the provided arrays with duplicates removed */
+export function unique(...arrays) {
+ const set = new Set();
+ for (const arr of arrays) {
+ for (const item of arr) {
+ set.add(item);
+ }
+ }
+ return [...set];
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cos.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cos.spec.js
new file mode 100644
index 0000000000..4f3a485b5f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cos.spec.js
@@ -0,0 +1,77 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'cos';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinus3PiTo3Pi,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */true,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cosh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cosh.spec.js
new file mode 100644
index 0000000000..27f975252c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/cosh.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'cosh';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(Math.cosh(t.params.value), elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/degrees.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/degrees.spec.js
new file mode 100644
index 0000000000..10aac98de2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/degrees.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'degrees';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(t.params.value * 180 / Math.PI, elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp.spec.js
new file mode 100644
index 0000000000..74a8816114
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp.spec.js
@@ -0,0 +1,102 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'exp';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 { kValue } from '../../../../../util/constants.js';
+import {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.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(kAllFloatScalarsAndVectors);
+
+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().
+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]
+)
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(Math.exp(t.params.value), elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp2.spec.js
new file mode 100644
index 0000000000..c38d5f90ef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/exp2.spec.js
@@ -0,0 +1,102 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'exp2';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 { kValue } from '../../../../../util/constants.js';
+import {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.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(kAllFloatScalarsAndVectors);
+
+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().
+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]
+)
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(Math.pow(2, t.params.value), elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.js
new file mode 100644
index 0000000000..bb6c089450
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.js
@@ -0,0 +1,81 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'inverseSqrt';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinusTwoToTwo,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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) => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ 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));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/length.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/length.spec.js
new file mode 100644
index 0000000000..12a28233e5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/length.spec.js
@@ -0,0 +1,221 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'length';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 {
+
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalars,
+ kAllFloatVector2,
+ kAllFloatVector3,
+ kAllFloatVector4,
+ kAllIntegerScalarsAndVectors } 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);
+
+/**
+ * Evaluates the result and information about a call to length(), with a vector
+ * formed from `vec` of the element type `type`.
+ */
+function calculate(
+vec,
+type)
+
+
+
+
+
+
+
+
+
+
+
+
+
+{
+ const squareSum = vec.reduce((prev, curr) => prev + curr * curr, 0);
+ const result = Math.sqrt(squareSum);
+ return {
+ isIntermediateRepresentable: isRepresentable(squareSum, type),
+ isResultRepresentable: isRepresentable(result, type),
+ result
+ };
+}
+
+const kScalarTypes = objectsToRecord(kAllFloatScalars);
+
+g.test('scalar').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() with
+the input scalar value always compiles without error
+`
+).
+params((u) =>
+u.
+combine('stage', kConstantAndOverrideStages).
+combine('type', keysOf(kScalarTypes)).
+filter((u) => stageSupportsType(u.stage, kScalarTypes[u.type])).
+beginSubcases().
+expand('value', (u) => fullRangeForType(kScalarTypes[u.type]))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kScalarTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ // We only validate with numbers known to be representable by the type
+ const expectedResult = true;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kScalarTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kVec2Types = objectsToRecord(kAllFloatVector2);
+
+g.test('vec2').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() with a vec2 compiles with valid values
+`
+).
+params((u) =>
+u.
+combine('stage', kConstantAndOverrideStages).
+combine('type', keysOf(kVec2Types)).
+filter((u) => stageSupportsType(u.stage, kVec2Types[u.type])).
+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]))]).
+filter((u) => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
+).
+beforeAllSubcases((t) => {
+ if (elementType(kVec2Types[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = t.params._result.isResultRepresentable;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kVec2Types[t.params.type].create([t.params.x, t.params.y])],
+ t.params.stage
+ );
+});
+
+const kVec3Types = objectsToRecord(kAllFloatVector3);
+
+g.test('vec3').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() with a vec3 compiles with valid values
+`
+).
+params((u) =>
+u.
+combine('stage', kConstantAndOverrideStages).
+combine('type', keysOf(kVec3Types)).
+filter((u) => stageSupportsType(u.stage, kVec3Types[u.type])).
+beginSubcases().
+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]))]).
+filter((u) => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
+).
+beforeAllSubcases((t) => {
+ if (elementType(kVec3Types[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = t.params._result.isResultRepresentable;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kVec3Types[t.params.type].create([t.params.x, t.params.y, t.params.z])],
+ t.params.stage
+ );
+});
+
+const kVec4Types = objectsToRecord(kAllFloatVector4);
+
+g.test('vec4').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() with a vec4 compiles with valid values
+`
+).
+params((u) =>
+u.
+combine('stage', kConstantAndOverrideStages).
+combine('type', keysOf(kVec4Types)).
+filter((u) => stageSupportsType(u.stage, kVec4Types[u.type])).
+beginSubcases().
+expand('x', (u) => fullRangeForType(kVec4Types[u.type], 3)).
+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]))]).
+filter((u) => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
+).
+beforeAllSubcases((t) => {
+ if (elementType(kVec4Types[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = t.params._result.isResultRepresentable;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kVec4Types[t.params.type].create([t.params.x, t.params.y, t.params.z, t.params.w])],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log.spec.js
new file mode 100644
index 0000000000..fb911b4188
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log.spec.js
@@ -0,0 +1,76 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'log';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = t.params.value > 0;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log2.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log2.spec.js
new file mode 100644
index 0000000000..4e538fbb77
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/log2.spec.js
@@ -0,0 +1,76 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'log2';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = t.params.value > 0;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/modf.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/modf.spec.js
new file mode 100644
index 0000000000..d3937bc4f3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/modf.spec.js
@@ -0,0 +1,76 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'modf';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // Result should always be representable by the type
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/radians.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/radians.spec.js
new file mode 100644
index 0000000000..51e1c451a3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/radians.spec.js
@@ -0,0 +1,76 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'radians';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // The result is always smaller than the input, so can't go OOB.
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/round.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/round.spec.js
new file mode 100644
index 0000000000..a90c17e10c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/round.spec.js
@@ -0,0 +1,84 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'round';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { fpTraitsFor } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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) => {
+ const constants = fpTraitsFor(elementType(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) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // Result should always be representable by the type
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/saturate.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/saturate.spec.js
new file mode 100644
index 0000000000..acf8bb059f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/saturate.spec.js
@@ -0,0 +1,76 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'saturate';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // Result should always be representable by the type
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sign.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sign.spec.js
new file mode 100644
index 0000000000..7a08bac5f9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sign.spec.js
@@ -0,0 +1,79 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'sign';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatAndSignedIntegerScalarsAndVectors,
+ kAllUnsignedIntegerScalarsAndVectors } 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(kAllFloatAndSignedIntegerScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const expectedResult = true; // Result should always be representable by the type
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kUnsignedIntegerArgumentTypes = objectsToRecord([
+TypeF32,
+...kAllUnsignedIntegerScalarsAndVectors]
+);
+
+g.test('unsigned_integer_argument').
+desc(
+ `
+Validates that scalar and vector integer arguments are rejected by ${builtin}()
+`
+).
+params((u) => u.combine('type', keysOf(kUnsignedIntegerArgumentTypes))).
+fn((t) => {
+ const type = kUnsignedIntegerArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */type === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sin.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sin.spec.js
new file mode 100644
index 0000000000..716311f20c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sin.spec.js
@@ -0,0 +1,77 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'sin';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinus3PiTo3Pi,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */true,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sinh.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sinh.spec.js
new file mode 100644
index 0000000000..c654132960
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sinh.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'sinh';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } 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(kAllFloatScalarsAndVectors);
+
+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 (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(Math.sinh(t.params.value), elementType(type));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sqrt.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sqrt.spec.js
new file mode 100644
index 0000000000..4a2f254fef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/sqrt.spec.js
@@ -0,0 +1,81 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'sqrt';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinusTwoToTwo,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+g.test('values').
+desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() inputs 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) => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ 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));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(1)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/tan.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/tan.spec.js
new file mode 100644
index 0000000000..91cee003ed
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/expression/call/builtin/tan.spec.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/const builtin = 'tan';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 {
+ TypeF16,
+ TypeF32,
+ elementType,
+ kAllFloatScalarsAndVectors,
+ kAllIntegerScalarsAndVectors } from
+'../../../../../util/conversion.js';
+import { fpTraitsFor } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ kMinus3PiTo3Pi,
+ stageSupportsType,
+ unique,
+ validateConstOrOverrideBuiltinEval } from
+'./const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+
+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) => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+).
+beforeAllSubcases((t) => {
+ if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const type = kValuesTypes[t.params.type];
+ const fp = fpTraitsFor(elementType(type));
+ const smallestPositive = fp.constants().positive.min;
+ const v = fp.quantize(t.params.value);
+ const expectedResult = Math.abs(Math.cos(v)) > smallestPositive;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+});
+
+const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+
+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 === TypeF32,
+ [type.create(0)],
+ 'constant'
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/alias_analysis.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/alias_analysis.spec.js
new file mode 100644
index 0000000000..ab5d7c3327
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/alias_analysis.spec.js
@@ -0,0 +1,202 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for function alias analysis`;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 kUses = {
+ no_access: { is_write: false, gen: (ref) => `{ let p = &*&${ref}; }` },
+ assign: { is_write: true, gen: (ref) => `${ref} = 42;` },
+ compound_assign_lhs: { is_write: true, gen: (ref) => `${ref} += 1;` },
+ compound_assign_rhs: { is_write: false, gen: (ref) => `{ var tmp : i32; tmp += ${ref}; }` },
+ increment: { is_write: true, gen: (ref) => `${ref}++;` },
+ binary_lhs: { is_write: false, gen: (ref) => `_ = ${ref} + 1;` },
+ binary_rhs: { is_write: false, gen: (ref) => `_ = 1 + ${ref};` },
+ unary_minus: { is_write: false, gen: (ref) => `_ = -${ref};` },
+ bitcast: { is_write: false, gen: (ref) => `_ = bitcast<f32>(${ref});` },
+ convert: { is_write: false, gen: (ref) => `_ = f32(${ref});` },
+ builtin_arg: { is_write: false, gen: (ref) => `_ = abs(${ref});` },
+ index_access: { is_write: false, gen: (ref) => `{ var arr : array<i32, 4>; _ = arr[${ref}]; }` },
+ let_init: { is_write: false, gen: (ref) => `{ let tmp = ${ref}; }` },
+ var_init: { is_write: false, gen: (ref) => `{ var tmp = ${ref}; }` },
+ return: { is_write: false, gen: (ref) => `{ return ${ref}; }` },
+ switch_cond: { is_write: false, gen: (ref) => `switch(${ref}) { default { break; } }` }
+};
+
+
+
+function shouldPass(aliased, ...uses) {
+ // Expect fail if the pointers are aliased and at least one of the accesses is a write.
+ // If either of the accesses is a "no access" then expect pass.
+ return !aliased || !uses.some((u) => kUses[u].is_write) || uses.includes('no_access');
+}
+
+g.test('two_pointers').
+desc(`Test aliasing of two pointers passed to a function.`).
+params((u) =>
+u.
+combine('address_space', ['private', 'function']).
+combine('a_use', keysOf(kUses)).
+combine('b_use', keysOf(kUses)).
+combine('aliased', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const code = `
+${t.params.address_space === 'private' ? `var<private> x : i32; var<private> y : 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`});
+}
+`;
+ 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('a_use', keysOf(kUses)).
+combine('b_use', keysOf(kUses)).
+combine('aliased', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const code = `
+var<private> x : i32;
+var<private> y : i32;
+
+fn callee(pb : ptr<private, i32>) -> i32 {
+ ${kUses[t.params.a_use].gen(`x`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn caller() {
+ callee(${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+});
+
+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']).
+combine('b_use', ['no_access', 'assign', 'binary_lhs']).
+combine('aliased', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const code = `
+var<private> x : i32;
+var<private> y : i32;
+
+fn subcall_no_access(p : ptr<private, i32>) {
+ let pp = &*p;
+}
+
+fn subcall_binary_lhs(p : ptr<private, i32>) -> i32 {
+ return *p + 1;
+}
+
+fn subcall_assign(p : ptr<private, i32>) {
+ *p = 42;
+}
+
+fn callee(pa : ptr<private, i32>, pb : ptr<private, i32>) -> i32 {
+ let new_pa = &*pa;
+ let new_pb = &*pb;
+ subcall_${t.params.a_use}(new_pa);
+ subcall_${t.params.b_use}(new_pb);
+ return 0;
+}
+
+fn caller() {
+ callee(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+});
+
+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']).
+combine('b_use', ['no_access', 'assign', 'binary_lhs']).
+combine('aliased', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const code = `
+struct S { a : i32 }
+
+var<private> x : S;
+var<private> y : S;
+
+fn callee(pa : ptr<private, S>,
+ pb : ptr<private, S>) -> i32 {
+ ${kUses[t.params.a_use].gen(`(*pa).a`)}
+ ${kUses[t.params.b_use].gen(`(*pb).a`)}
+ return 0;
+}
+
+fn caller() {
+ callee(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+});
+
+g.test('same_pointer_read_and_write').
+desc(`Test that we can read from and write to the same pointer.`).
+params((u) => u.beginSubcases()).
+fn((t) => {
+ const code = `
+var<private> v : i32;
+
+fn callee(p : ptr<private, i32>) {
+ *p = *p + 1;
+}
+
+fn caller() {
+ callee(&v);
+}
+`;
+ t.expectCompileResult(true, code);
+});
+
+g.test('aliasing_inside_function').
+desc(`Test that we can alias pointers inside a function.`).
+params((u) => u.beginSubcases()).
+fn((t) => {
+ const code = `
+var<private> v : i32;
+
+fn foo() {
+ var v : i32;
+ let p1 = &v;
+ let p2 = &v;
+ *p1 = 42;
+ *p2 = 42;
+}
+`;
+ t.expectCompileResult(true, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/restrictions.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/restrictions.spec.js
new file mode 100644
index 0000000000..94e38837d9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/functions/restrictions.spec.js
@@ -0,0 +1,757 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for function restrictions`;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 kVertexPosCases = {
+ bare_position: { name: `@builtin(position) vec4f`, value: `vec4f()`, valid: true },
+ nested_position: { name: `pos_struct`, value: `pos_struct()`, valid: true },
+ no_bare_position: { name: `vec4f`, value: `vec4f()`, valid: false },
+ no_nested_position: { name: `no_pos_struct`, value: `no_pos_struct()`, valid: false }
+};
+
+g.test('vertex_returns_position').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(`Test that a vertex shader should return position`).
+params((u) => u.combine('case', keysOf(kVertexPosCases))).
+fn((t) => {
+ const testcase = kVertexPosCases[t.params.case];
+ const code = `
+struct pos_struct {
+ @builtin(position) pos : vec4f
+}
+
+struct no_pos_struct {
+ @location(0) x : vec4f
+}
+
+@vertex
+fn main() -> ${testcase.name} {
+ return ${testcase.value};
+}`;
+
+ t.expectCompileResult(testcase.valid, code);
+});
+
+g.test('entry_point_call_target').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(`Test that an entry point cannot be the target of a function call`).
+params((u) =>
+u.
+combine('stage', ['@fragment', '@vertex', '@compute @workgroup_size(1,1,1)']).
+combine('entry_point', ['with', 'without'])
+).
+fn((t) => {
+ const use_attr = t.params.entry_point === 'with';
+ let ret_attr = '';
+ if (use_attr && t.params.stage === '@vertex') {
+ ret_attr = '@builtin(position)';
+ }
+ const ret = t.params.stage.indexOf('@vertex') === 0 ? `-> ${ret_attr} vec4f` : '';
+ const ret_value = t.params.stage.indexOf('@vertex') === 0 ? `return vec4f();` : '';
+ const call = t.params.stage.indexOf('@vertex') === 0 ? 'let tmp = bar();' : 'bar();';
+ const stage_attr = use_attr ? t.params.stage : '';
+ const code = `
+${stage_attr}
+fn bar() ${ret} {
+ ${ret_value}
+}
+
+fn foo() {
+ ${call}
+}
+`;
+ t.expectCompileResult(!use_attr, code);
+});
+
+
+
+
+
+
+
+const kFunctionRetTypeCases = {
+ // Constructible types,
+ u32: { name: `u32`, value: ``, valid: true },
+ i32: { name: `i32`, value: ``, valid: true },
+ f32: { name: `f32`, value: ``, valid: true },
+ bool: { name: `bool`, value: ``, valid: true },
+ f16: { name: `f16`, value: ``, valid: true },
+ vec2: { name: `vec2u`, value: ``, valid: true },
+ vec3: { name: `vec3i`, value: ``, valid: true },
+ vec4: { name: `vec4f`, value: ``, valid: true },
+ mat2x2: { name: `mat2x2f`, value: ``, valid: true },
+ mat2x3: { name: `mat2x3f`, value: ``, valid: true },
+ mat2x4: { name: `mat2x4f`, value: ``, valid: true },
+ mat3x2: { name: `mat3x2f`, value: ``, valid: true },
+ mat3x3: { name: `mat3x3f`, value: ``, valid: true },
+ mat3x4: { name: `mat3x4f`, value: ``, valid: true },
+ mat4x2: { name: `mat4x2f`, value: ``, valid: true },
+ mat4x3: { name: `mat4x3f`, value: ``, valid: true },
+ mat4x4: { name: `mat4x4f`, value: ``, valid: true },
+ array1: { name: `array<u32, 4>`, value: ``, valid: true },
+ array2: { name: `array<vec2f, 2>`, value: ``, valid: true },
+ array3: { name: `array<constructible, 4>`, value: ``, valid: true },
+ array4: { name: `array<mat2x2f, 4>`, value: ``, valid: true },
+ array5: { name: `array<bool, 4>`, value: ``, valid: true },
+ struct1: { name: `constructible`, value: ``, valid: true },
+ struct2: { name: `struct_with_array`, value: ``, valid: true },
+
+ // Non-constructible types.
+ runtime_array: { name: `array<u32>`, value: ``, valid: false },
+ runtime_struct: { name: `runtime_array_struct`, value: ``, valid: false },
+ override_array: { name: `array<u32, override_size>`, value: ``, valid: false },
+ atomic_u32: { name: `atomic<u32>`, value: `atomic_wg`, valid: false },
+ atomic_struct: { name: `atomic_struct`, value: ``, valid: false },
+ texture_sample: { name: `texture_2d<f32>`, value: `t`, valid: false },
+ texture_depth: { name: `texture_depth_2d`, value: `t_depth`, valid: false },
+ texture_multisampled: {
+ name: `texture_multisampled_2d<f32>`,
+ value: `t_multisampled`,
+ valid: false
+ },
+ texture_storage: {
+ name: `texture_storage_2d<rgba8unorm, write>`,
+ value: `t_storage`,
+ valid: false
+ },
+ sampler: { name: `sampler`, value: `s`, valid: false },
+ sampler_comparison: { name: `sampler_comparison`, value: `s_depth`, valid: false },
+ ptr: { name: `ptr<workgroup, atomic<u32>>`, value: `&atomic_wg`, valid: false }
+};
+
+g.test('function_return_types').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(`Test that function return types must be constructible`).
+params((u) => u.combine('case', keysOf(kFunctionRetTypeCases))).
+beforeAllSubcases((t) => {
+ if (kFunctionRetTypeCases[t.params.case].name === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const testcase = kFunctionRetTypeCases[t.params.case];
+ const enable = testcase.name === 'f16' ? 'enable f16;' : '';
+ const value = testcase.value === '' ? `${testcase.name}()` : testcase.value;
+ 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>
+}
+
+struct atomic_struct {
+ a : atomic<u32>
+};
+
+override override_size : u32;
+
+var<workgroup> atomic_wg : atomic<u32>;
+
+@group(0) @binding(0)
+var t : texture_2d<f32>;
+@group(0) @binding(1)
+var s : sampler;
+@group(0) @binding(2)
+var s_depth : sampler_comparison;
+@group(0) @binding(3)
+var t_storage : texture_storage_2d<rgba8unorm, write>;
+@group(0) @binding(4)
+var t_depth : texture_depth_2d;
+@group(0) @binding(5)
+var t_multisampled : texture_multisampled_2d<f32>;
+@group(0) @binding(6)
+var t_external : texture_external;
+
+fn foo() -> ${testcase.name} {
+ return ${value};
+}`;
+
+ t.expectCompileResult(testcase.valid, code);
+});
+
+
+
+
+
+
+const kFunctionParamTypeCases = {
+ // Constructible types,
+ u32: { name: `u32`, valid: true },
+ i32: { name: `i32`, valid: true },
+ f32: { name: `f32`, valid: true },
+ bool: { name: `bool`, valid: true },
+ f16: { name: `f16`, valid: true },
+ vec2: { name: `vec2u`, valid: true },
+ vec3: { name: `vec3i`, valid: true },
+ vec4: { name: `vec4f`, valid: true },
+ mat2x2: { name: `mat2x2f`, valid: true },
+ mat2x3: { name: `mat2x3f`, valid: true },
+ mat2x4: { name: `mat2x4f`, valid: true },
+ mat3x2: { name: `mat3x2f`, valid: true },
+ mat3x3: { name: `mat3x3f`, valid: true },
+ mat3x4: { name: `mat3x4f`, valid: true },
+ mat4x2: { name: `mat4x2f`, valid: true },
+ mat4x3: { name: `mat4x3f`, valid: true },
+ mat4x4: { name: `mat4x4f`, valid: true },
+ array1: { name: `array<u32, 4>`, valid: true },
+ array2: { name: `array<vec2f, 2>`, valid: true },
+ array3: { name: `array<constructible, 4>`, valid: true },
+ array4: { name: `array<mat2x2f, 4>`, valid: true },
+ array5: { name: `array<bool, 4>`, valid: true },
+ struct1: { name: `constructible`, valid: true },
+ struct2: { name: `struct_with_array`, valid: true },
+
+ // Non-constructible types.
+ runtime_array: { name: `array<u32>`, valid: false },
+ runtime_struct: { name: `runtime_array_struct`, valid: false },
+ override_array: { name: `array<u32, override_size>`, valid: false },
+ atomic_u32: { name: `atomic<u32>`, valid: false },
+ atomic_struct: { name: `atomic_struct`, valid: false },
+
+ // Textures and samplers.
+ texture_sample: { name: `texture_2d<f32>`, valid: true },
+ texture_depth: { name: `texture_depth_2d`, valid: true },
+ texture_multisampled: {
+ name: `texture_multisampled_2d<f32>`,
+ valid: true
+ },
+ texture_storage: { name: `texture_storage_2d<rgba8unorm, write>`, valid: true },
+ sampler: { name: `sampler`, valid: true },
+ sampler_comparison: { name: `sampler_comparison`, valid: true },
+
+ // Valid pointers.
+ ptr1: { name: `ptr<function, u32>`, valid: true },
+ ptr2: { name: `ptr<function, constructible>`, valid: true },
+ ptr3: { name: `ptr<private, u32>`, valid: true },
+ ptr4: { name: `ptr<private, constructible>`, valid: true },
+
+ // 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 }
+};
+
+g.test('function_parameter_types').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(`Test validation of user-declared function parameter types`).
+params((u) => u.combine('case', keysOf(kFunctionParamTypeCases))).
+beforeAllSubcases((t) => {
+ if (kFunctionParamTypeCases[t.params.case].name === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const testcase = kFunctionParamTypeCases[t.params.case];
+ const enable = testcase.name === 'f16' ? 'enable f16;' : '';
+ 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>
+}
+
+fn foo(param : ${testcase.name}) {
+}`;
+
+ t.expectCompileResult(testcase.valid, code);
+});
+
+
+
+
+
+
+const kFunctionParamValueCases = {
+ // Values
+ u32_literal: { value: `0u`, matches: ['u32'] },
+ i32_literal: { value: `0i`, matches: ['i32'] },
+ f32_literal: { value: `0f`, matches: ['f32'] },
+ bool_literal: { value: `false`, matches: ['bool'] },
+ abstract_int_literal: { value: `0`, matches: ['u32', 'i32', 'f32', 'f16'] },
+ abstract_float_literal: { value: `0.0`, matches: ['f32', 'f16'] },
+ vec2u_constructor: { value: `vec2u()`, matches: ['vec2'] },
+ vec2i_constructor: { value: `vec2i()`, matches: [] },
+ vec2f_constructor: { value: `vec2f()`, matches: [] },
+ vec2b_constructor: { value: `vec2<bool>()`, matches: [] },
+ vec3u_constructor: { value: `vec3u()`, matches: [] },
+ vec3i_constructor: { value: `vec3i()`, matches: ['vec3'] },
+ vec3f_constructor: { value: `vec3f()`, matches: [] },
+ vec3b_constructor: { value: `vec3<bool>()`, matches: [] },
+ vec4u_constructor: { value: `vec4u()`, matches: [] },
+ vec4i_constructor: { value: `vec4i()`, matches: [] },
+ vec4f_constructor: { value: `vec4f()`, matches: ['vec4'] },
+ vec4b_constructor: { value: `vec4<bool>()`, matches: [] },
+ vec2_abstract_int: { value: `vec2(0,0)`, matches: ['vec2'] },
+ vec2_abstract_float: { value: `vec2(0.0,0)`, matches: [] },
+ vec3_abstract_int: { value: `vec3(0,0,0)`, matches: ['vec3'] },
+ vec3_abstract_float: { value: `vec3(0.0,0,0)`, matches: [] },
+ vec4_abstract_int: { value: `vec4(0,0,0,0)`, matches: ['vec4'] },
+ vec4_abstract_float: { value: `vec4(0.0,0,0,0)`, matches: ['vec4'] },
+ mat2x2_constructor: { value: `mat2x2f()`, matches: ['mat2x2'] },
+ mat2x3_constructor: { value: `mat2x3f()`, matches: ['mat2x3'] },
+ mat2x4_constructor: { value: `mat2x4f()`, matches: ['mat2x4'] },
+ mat3x2_constructor: { value: `mat3x2f()`, matches: ['mat3x2'] },
+ mat3x3_constructor: { value: `mat3x3f()`, matches: ['mat3x3'] },
+ mat3x4_constructor: { value: `mat3x4f()`, matches: ['mat3x4'] },
+ mat4x2_constructor: { value: `mat4x2f()`, matches: ['mat4x2'] },
+ mat4x3_constructor: { value: `mat4x3f()`, matches: ['mat4x3'] },
+ mat4x4_constructor: { value: `mat4x4f()`, matches: ['mat4x4'] },
+ array1_constructor: { value: `array<u32, 4>()`, matches: ['array1'] },
+ array2_constructor: { value: `array<vec2f, 2>()`, matches: ['array2'] },
+ array3_constructor: { value: `array<constructible, 4>()`, matches: ['array3'] },
+ array4_constructor: { value: `array<mat2x2f, 4>()`, matches: ['array4'] },
+ array5_constructor: { value: `array<bool, 4>()`, matches: ['array5'] },
+ struct1_constructor: { value: `constructible()`, matches: ['struct1'] },
+ struct2_constructor: { value: `struct_with_array()`, matches: ['struct2'] },
+
+ // Variable references
+ g_u32: { value: `g_u32`, matches: ['u32'] },
+ g_i32: { value: `g_i32`, matches: ['i32'] },
+ g_f32: { value: `g_f32`, matches: ['f32'] },
+ g_bool: { value: `g_bool`, matches: ['bool'] },
+ g_vec2: { value: `g_vec2`, matches: ['vec2'] },
+ g_vec3: { value: `g_vec3`, matches: ['vec3'] },
+ g_vec4: { value: `g_vec4`, matches: ['vec4'] },
+ g_mat2x2: { value: `g_mat2x2`, matches: ['mat2x2'] },
+ g_mat2x3: { value: `g_mat2x3`, matches: ['mat2x3'] },
+ g_mat2x4: { value: `g_mat2x4`, matches: ['mat2x4'] },
+ g_mat3x2: { value: `g_mat3x2`, matches: ['mat3x2'] },
+ g_mat3x3: { value: `g_mat3x3`, matches: ['mat3x3'] },
+ g_mat3x4: { value: `g_mat3x4`, matches: ['mat3x4'] },
+ g_mat4x2: { value: `g_mat4x2`, matches: ['mat4x2'] },
+ g_mat4x3: { value: `g_mat4x3`, matches: ['mat4x3'] },
+ g_mat4x4: { value: `g_mat4x4`, matches: ['mat4x4'] },
+ g_array1: { value: `g_array1`, matches: ['array1'] },
+ g_array2: { value: `g_array2`, matches: ['array2'] },
+ g_array3: { value: `g_array3`, matches: ['array3'] },
+ g_array4: { value: `g_array4`, matches: ['array4'] },
+ g_array5: { value: `g_array5`, matches: ['array5'] },
+ g_constructible: { value: `g_constructible`, matches: ['struct1'] },
+ g_struct_with_array: { value: `g_struct_with_array`, matches: ['struct2'] },
+ f_u32: { value: `f_u32`, matches: ['u32'] },
+ f_i32: { value: `f_i32`, matches: ['i32'] },
+ f_f32: { value: `f_f32`, matches: ['f32'] },
+ f_bool: { value: `f_bool`, matches: ['bool'] },
+ f_vec2: { value: `f_vec2`, matches: ['vec2'] },
+ f_vec3: { value: `f_vec3`, matches: ['vec3'] },
+ f_vec4: { value: `f_vec4`, matches: ['vec4'] },
+ f_mat2x2: { value: `f_mat2x2`, matches: ['mat2x2'] },
+ f_mat2x3: { value: `f_mat2x3`, matches: ['mat2x3'] },
+ f_mat2x4: { value: `f_mat2x4`, matches: ['mat2x4'] },
+ f_mat3x2: { value: `f_mat3x2`, matches: ['mat3x2'] },
+ f_mat3x3: { value: `f_mat3x3`, matches: ['mat3x3'] },
+ f_mat3x4: { value: `f_mat3x4`, matches: ['mat3x4'] },
+ f_mat4x2: { value: `f_mat4x2`, matches: ['mat4x2'] },
+ f_mat4x3: { value: `f_mat4x3`, matches: ['mat4x3'] },
+ f_mat4x4: { value: `f_mat4x4`, matches: ['mat4x4'] },
+ f_array1: { value: `f_array1`, matches: ['array1'] },
+ f_array2: { value: `f_array2`, matches: ['array2'] },
+ f_array3: { value: `f_array3`, matches: ['array3'] },
+ f_array4: { value: `f_array4`, matches: ['array4'] },
+ f_array5: { value: `f_array5`, matches: ['array5'] },
+ f_constructible: { value: `f_constructible`, matches: ['struct1'] },
+ f_struct_with_array: { value: `f_struct_with_array`, matches: ['struct2'] },
+ g_index_u32: { value: `g_constructible.b`, matches: ['u32'] },
+ g_index_i32: { value: `g_constructible.a`, matches: ['i32'] },
+ g_index_f32: { value: `g_constructible.c`, matches: ['f32'] },
+ g_index_bool: { value: `g_constructible.d`, matches: ['bool'] },
+ f_index_u32: { value: `f_constructible.b`, matches: ['u32'] },
+ f_index_i32: { value: `f_constructible.a`, matches: ['i32'] },
+ f_index_f32: { value: `f_constructible.c`, matches: ['f32'] },
+ f_index_bool: { value: `f_constructible.d`, matches: ['bool'] },
+ g_array_index_u32: { value: `g_struct_with_array.a[0].b`, matches: ['u32'] },
+ g_array_index_i32: { value: `g_struct_with_array.a[1].a`, matches: ['i32'] },
+ g_array_index_f32: { value: `g_struct_with_array.a[2].c`, matches: ['f32'] },
+ g_array_index_bool: { value: `g_struct_with_array.a[3].d`, matches: ['bool'] },
+ f_array_index_u32: { value: `f_struct_with_array.a[0].b`, matches: ['u32'] },
+ f_array_index_i32: { value: `f_struct_with_array.a[1].a`, matches: ['i32'] },
+ f_array_index_f32: { value: `f_struct_with_array.a[2].c`, matches: ['f32'] },
+ f_array_index_bool: { value: `f_struct_with_array.a[3].d`, matches: ['bool'] },
+
+ // Textures and samplers
+ texture_sample: { value: `t`, matches: ['texture_sample'] },
+ texture_depth: { value: `t_depth`, matches: ['texture_depth'] },
+ texture_multisampled: { value: `t_multisampled`, matches: ['texture_multisampled'] },
+ texture_storage: { value: `t_storage`, matches: ['texture_storage'] },
+ texture_external: { value: `t_external`, matches: ['texture_external'] },
+ sampler: { value: `s`, matches: ['sampler'] },
+ sampler_comparison: { value: `s_depth`, matches: ['sampler_comparison'] },
+
+ // Pointers
+ ptr1: { value: `&f_u32`, matches: ['ptr1'] },
+ ptr2: { value: `&f_constructible`, matches: ['ptr2'] },
+ 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: [] }
+};
+
+function parameterMatches(decl, matches) {
+ for (const val of matches) {
+ if (decl === val) {
+ return true;
+ }
+ }
+ return false;
+}
+
+g.test('function_parameter_matching').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(
+ `Test that function parameter types match function parameter type on user-declared functions`
+).
+params((u) =>
+u.
+combine('decl', keysOf(kFunctionParamTypeCases)).
+combine('arg', keysOf(kFunctionParamValueCases)).
+filter((u) => {
+ return kFunctionParamTypeCases[u.decl].valid;
+})
+).
+beforeAllSubcases((t) => {
+ if (kFunctionParamTypeCases[t.params.decl].name === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const param = kFunctionParamTypeCases[t.params.decl];
+ const arg = kFunctionParamValueCases[t.params.arg];
+ const enable = param.name === 'f16' ? 'enable f16;' : '';
+ 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>
+}
+@group(0) @binding(0)
+var t : texture_2d<f32>;
+@group(0) @binding(1)
+var s : sampler;
+@group(0) @binding(2)
+var s_depth : sampler_comparison;
+@group(0) @binding(3)
+var t_storage : texture_storage_2d<rgba8unorm, write>;
+@group(0) @binding(4)
+var t_depth : texture_depth_2d;
+@group(0) @binding(5)
+var t_multisampled : texture_multisampled_2d<f32>;
+@group(0) @binding(6)
+var t_external : texture_external;
+
+@group(1) @binding(0)
+var<storage> ro_constructible : host_shareable;
+@group(1) @binding(1)
+var<storage, read_write> rw_constructible : host_shareable;
+@group(1) @binding(2)
+var<uniform> uniform_constructible : host_shareable;
+
+fn bar(param : ${param.name}) { }
+
+var<private> g_u32 : u32;
+var<private> g_i32 : i32;
+var<private> g_f32 : f32;
+var<private> g_bool : bool;
+var<private> g_vec2 : vec2u;
+var<private> g_vec3 : vec3i;
+var<private> g_vec4 : vec4f;
+var<private> g_mat2x2 : mat2x2f;
+var<private> g_mat2x3 : mat2x3f;
+var<private> g_mat2x4 : mat2x4f;
+var<private> g_mat3x2 : mat3x2f;
+var<private> g_mat3x3 : mat3x3f;
+var<private> g_mat3x4 : mat3x4f;
+var<private> g_mat4x2 : mat4x2f;
+var<private> g_mat4x3 : mat4x3f;
+var<private> g_mat4x4 : mat4x4f;
+var<private> g_array1 : array<u32, 4>;
+var<private> g_array2 : array<vec2f, 2>;
+var<private> g_array3 : array<constructible, 4>;
+var<private> g_array4 : array<mat2x2f, 4>;
+var<private> g_array5 : array<bool, 4>;
+var<private> g_constructible : constructible;
+var<private> g_struct_with_array : struct_with_array;
+
+fn foo() {
+ var f_u32 : u32;
+ var f_i32 : i32;
+ var f_f32 : f32;
+ var f_bool : bool;
+ var f_vec2 : vec2u;
+ var f_vec3 : vec3i;
+ var f_vec4 : vec4f;
+ var f_mat2x2 : mat2x2f;
+ var f_mat2x3 : mat2x3f;
+ var f_mat2x4 : mat2x4f;
+ var f_mat3x2 : mat3x2f;
+ var f_mat3x3 : mat3x3f;
+ var f_mat3x4 : mat3x4f;
+ var f_mat4x2 : mat4x2f;
+ var f_mat4x3 : mat4x3f;
+ var f_mat4x4 : mat4x4f;
+ var f_array1 : array<u32, 4>;
+ var f_array2 : array<vec2f, 2>;
+ var f_array3 : array<constructible, 4>;
+ var f_array4 : array<mat2x2f, 4>;
+ var f_array5 : array<bool, 4>;
+ var f_constructible : constructible;
+ var f_struct_with_array : struct_with_array;
+
+ bar(${arg.value});
+}
+`;
+
+ t.expectCompileResult(parameterMatches(t.params.decl, arg.matches), code);
+});
+
+g.test('no_direct_recursion').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(`Test that functions cannot be directly recursive`).
+fn((t) => {
+ const code = `
+fn foo() {
+ foo();
+}`;
+
+ t.expectCompileResult(false, code);
+});
+
+g.test('no_indirect_recursion').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-restriction').
+desc(`Test that functions cannot be indirectly recursive`).
+fn((t) => {
+ const code = `
+fn bar() {
+ foo();
+}
+fn foo() {
+ bar();
+}`;
+
+ t.expectCompileResult(false, code);
+});
+
+g.test('param_names_must_differ').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-declaration-sec').
+desc(`Test that function parameters must have different names`).
+params((u) => u.combine('p1', ['a', 'b', 'c']).combine('p2', ['a', 'b', 'c'])).
+fn((t) => {
+ const code = `fn foo(${t.params.p1} : u32, ${t.params.p2} : f32) { }`;
+ t.expectCompileResult(t.params.p1 !== t.params.p2, code);
+});
+
+const kParamUseCases = {
+ body: `fn foo(param : u32) {
+ let tmp = param;
+ }`,
+ var: `var<private> v : u32 = param;
+ fn foo(param : u32) { }`,
+ const: `const c : u32 = param;
+ fn foo(param : u32) { }`,
+ override: `override o : u32 = param;
+ fn foo(param : u32) { }`,
+ function: `fn bar() { let tmp = param; }
+ fn foo(param : u32) { }`
+};
+
+g.test('param_scope_is_function_body').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-declaration-sec').
+desc(`Test that function parameters are only in scope in the function body`).
+params((u) => u.combine('use', keysOf(kParamUseCases))).
+fn((t) => {
+ t.expectCompileResult(t.params.use === 'body', kParamUseCases[t.params.use]);
+});
+
+g.test('param_number_matches_call').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls').
+desc(`Test that function calls have an equal number of arguments as the number of parameters`).
+params((u) =>
+u.
+combine('num_args', [0, 1, 2, 3, 4, 255]).
+combine('num_params', [0, 1, 2, 3, 4, 255])
+).
+fn((t) => {
+ let code = `
+ fn bar(`;
+ for (let i = 0; i < t.params.num_params; i++) {
+ code += `p${i} : u32,`;
+ }
+ code += `) { }\n`;
+ code += `fn foo() {\nbar(`;
+ for (let i = 0; i < t.params.num_args; i++) {
+ code += `0,`;
+ }
+ code += `);\n}`;
+ t.expectCompileResult(t.params.num_args === t.params.num_params, code);
+});
+
+const kParamsTypes = ['u32', 'i32', 'f32'];
+
+
+
+
+
+
+const kArgValues = {
+ abstract_int: {
+ value: '0',
+ matches: ['u32', 'i32', 'f32']
+ },
+ abstract_float: {
+ value: '0.0',
+ matches: ['f32']
+ },
+ unsigned_int: {
+ value: '0u',
+ matches: ['u32']
+ },
+ signed_int: {
+ value: '0i',
+ matches: ['i32']
+ },
+ float: {
+ value: '0f',
+ matches: ['f32']
+ }
+};
+
+function checkArgTypeMatch(param_type, arg_matches) {
+ for (const match of arg_matches) {
+ if (match === param_type) {
+ return true;
+ }
+ }
+ return false;
+}
+
+g.test('call_arg_types_match_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]).
+combine('p1_type', kParamsTypes).
+combine('p2_type', kParamsTypes).
+combine('p3_type', kParamsTypes).
+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}`;
+
+ 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);
+ }
+ t.expectCompileResult(res, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/align.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/align.spec.js
new file mode 100644
index 0000000000..e0fae5a693
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/align.spec.js
@@ -0,0 +1,341 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for @align`;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 = {
+ blank: {
+ src: '',
+ pass: true
+ },
+ one: {
+ src: '@align(1)',
+ pass: true
+ },
+ four_a: {
+ src: '@align(4)',
+ pass: true
+ },
+ four_i: {
+ src: '@align(4i)',
+ pass: true
+ },
+ four_u: {
+ src: '@align(4u)',
+ pass: true
+ },
+ four_hex: {
+ src: '@align(0x4)',
+ pass: true
+ },
+ trailing_comma: {
+ src: '@align(4,)',
+ pass: true
+ },
+ const_u: {
+ src: '@align(u_val)',
+ pass: true
+ },
+ const_i: {
+ src: '@align(i_val)',
+ pass: true
+ },
+ const_expr: {
+ src: '@align(i_val + 4 - 6)',
+ pass: true
+ },
+ large: {
+ src: '@align(1073741824)',
+ pass: true
+ },
+ tabs: {
+ src: '@\talign\t(4)',
+ pass: true
+ },
+ comment: {
+ src: '@/*comment*/align/*comment*/(4)',
+ pass: true
+ },
+ misspelling: {
+ src: '@malign(4)',
+ pass: false
+ },
+ empty: {
+ src: '@align()',
+ pass: false
+ },
+ missing_left_paren: {
+ src: '@align 4)',
+ pass: false
+ },
+ missing_right_paren: {
+ src: '@align(4',
+ pass: false
+ },
+ multiple_values: {
+ src: '@align(4, 2)',
+ pass: false
+ },
+ non_power_two: {
+ src: '@align(3)',
+ pass: false
+ },
+ const_f: {
+ src: '@align(f_val)',
+ pass: false
+ },
+ one_f: {
+ src: '@align(1.0)',
+ pass: false
+ },
+ four_f: {
+ src: '@align(4f)',
+ pass: false
+ },
+ four_h: {
+ src: '@align(4h)',
+ pass: false
+ },
+ no_params: {
+ src: '@align',
+ pass: false
+ },
+ zero_a: {
+ src: '@align(0)',
+ pass: false
+ },
+ negative: {
+ src: '@align(-4)',
+ pass: false
+ },
+ large_no_power_two: {
+ src: '@align(2147483646)',
+ pass: false
+ },
+ larger_than_max_i32: {
+ src: '@align(2147483648)',
+ pass: false
+ }
+};
+
+g.test('parsing').
+desc(`Test that @align is parsed correctly.`).
+params((u) => u.combine('align', keysOf(kTests))).
+fn((t) => {
+ const src = kTests[t.params.align].src;
+ const code = `
+const i_val: i32 = 4;
+const u_val: u32 = 4;
+const f_val: f32 = 4.2;
+struct B {
+ ${src} a: i32,
+}
+
+@group(0) @binding(0)
+var<uniform> uniform_buffer: B;
+
+@fragment
+fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.align].pass, code);
+});
+
+g.test('required_alignment').
+desc('Test that the align with an invalid size is an error').
+params((u) =>
+u.
+combine('address_space', ['storage', 'uniform']).
+combine('align', [1, 2, 'alignment', 32]).
+combine('type', [
+{ name: 'i32', storage: 4, uniform: 4 },
+{ name: 'u32', storage: 4, uniform: 4 },
+{ name: 'f32', storage: 4, uniform: 4 },
+{ name: 'f16', storage: 2, uniform: 2 },
+{ name: 'atomic<i32>', storage: 4, uniform: 4 },
+{ name: 'vec2<i32>', storage: 8, uniform: 8 },
+{ name: 'vec2<f16>', storage: 4, uniform: 4 },
+{ name: 'vec3<u32>', storage: 16, uniform: 16 },
+{ name: 'vec3<f16>', storage: 8, uniform: 8 },
+{ name: 'vec4<f32>', storage: 16, uniform: 16 },
+{ name: 'vec4<f16>', storage: 8, uniform: 8 },
+{ name: 'mat2x2<f32>', storage: 8, uniform: 8 },
+{ name: 'mat3x2<f32>', storage: 8, uniform: 8 },
+{ name: 'mat4x2<f32>', storage: 8, uniform: 8 },
+{ name: 'mat2x2<f16>', storage: 4, uniform: 4 },
+{ name: 'mat3x2<f16>', storage: 4, uniform: 4 },
+{ name: 'mat4x2<f16>', storage: 4, uniform: 4 },
+{ name: 'mat2x3<f32>', storage: 16, uniform: 16 },
+{ name: 'mat3x3<f32>', storage: 16, uniform: 16 },
+{ name: 'mat4x3<f32>', storage: 16, uniform: 16 },
+{ name: 'mat2x3<f16>', storage: 8, uniform: 8 },
+{ name: 'mat3x3<f16>', storage: 8, uniform: 8 },
+{ name: 'mat4x3<f16>', storage: 8, uniform: 8 },
+{ name: 'mat2x4<f32>', storage: 16, uniform: 16 },
+{ name: 'mat3x4<f32>', storage: 16, uniform: 16 },
+{ name: 'mat4x4<f32>', storage: 16, uniform: 16 },
+{ name: 'mat2x4<f16>', storage: 8, uniform: 8 },
+{ name: 'mat3x4<f16>', storage: 8, uniform: 8 },
+{ name: 'mat4x4<f16>', storage: 8, uniform: 8 },
+{ name: 'array<vec2<i32>, 2>', storage: 8, uniform: 16 },
+{ name: 'array<vec4<i32>, 2>', storage: 8, uniform: 16 },
+{ name: 'S', storage: 8, uniform: 16 }]
+).
+beginSubcases()
+).
+beforeAllSubcases((t) => {
+ if (t.params.type.name.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ // While this would fail validation, it doesn't fail for any reasons related to alignment.
+ // Atomics are not allowed in uniform address space as they have to be read_write.
+ if (t.params.address_space === 'uniform' && t.params.type.name.startsWith('atomic')) {
+ t.skip('No atomics in uniform address space');
+ }
+
+ let code = '';
+ if (t.params.type.name.includes('f16')) {
+ code += 'enable f16;\n';
+ }
+
+ // Testing the struct case, generate the structf
+ if (t.params.type.name === 'S') {
+ code += `struct S {
+ a: mat4x2<f32>, // Align 8
+ b: array<vec${
+ t.params.address_space === 'storage' ? 2 : 4
+ }<i32>, 2>, // Storage align 8, uniform 16
+ }
+ `;
+ }
+
+ let align = t.params.align;
+ if (t.params.align === 'alignment') {
+ // Alignment value listed in the spec
+ if (t.params.address_space === 'storage') {
+ align = `${t.params.type.storage}`;
+ } else {
+ align = `${t.params.type.uniform}`;
+ }
+ }
+
+ let address_space = 'uniform';
+ if (t.params.address_space === 'storage') {
+ // atomics require read_write, not just the default of read
+ address_space = 'storage, read_write';
+ }
+
+ code += `struct MyStruct {
+ @align(${align}) a: ${t.params.type.name},
+ }
+
+ @group(0) @binding(0)
+ var<${address_space}> a : MyStruct;`;
+
+ code += `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+
+ // An array of `vec2` in uniform will not validate because, while the alignment on the array
+ // itself is fine, the `vec2` element inside the array will have the wrong alignment. Uniform
+ // requires that inner vec2 to have an align 16 which can only be done by specifying `vec4`
+ // instead.
+ const fails =
+ t.params.address_space === 'uniform' && t.params.type.name.startsWith('array<vec2');
+
+ t.expectCompileResult(!fails, code);
+});
+
+g.test('placement').
+desc('Tests the locations @align is allowed to appear').
+params((u) =>
+u.
+combine('scope', [
+'private-var',
+'storage-var',
+'struct-member',
+'fn-decl',
+'fn-param',
+'fn-var',
+'fn-return',
+'while-stmt',
+undefined]
+).
+combine('attribute', [
+{
+ 'private-var': false,
+ 'storage-var': false,
+ 'struct-member': true,
+ 'fn-decl': false,
+ 'fn-param': false,
+ 'fn-var': false,
+ 'fn-return': false,
+ 'while-stmt': false
+}]
+).
+beginSubcases()
+).
+fn((t) => {
+ const scope = t.params.scope;
+
+ const attr = '@align(32)';
+ const code = `
+ ${scope === 'private-var' ? attr : ''}
+ var<private> priv_var : i32;
+
+ ${scope === 'storage-var' ? attr : ''}
+ @group(0) @binding(0)
+ var<storage> stor_var : i32;
+
+ struct A {
+ ${scope === 'struct-member' ? attr : ''}
+ a : i32,
+ }
+
+ @vertex
+ ${scope === 'fn-decl' ? attr : ''}
+ fn f(
+ ${scope === 'fn-param' ? attr : ''}
+ @location(0) b : i32,
+ ) -> ${scope === 'fn-return' ? attr : ''} @builtin(position) vec4f {
+ ${scope === 'fn-var' ? attr : ''}
+ var<function> func_v : i32;
+
+ ${scope === 'while-stmt' ? attr : ''}
+ while false {}
+
+ return vec4(1, 1, 1, 1);
+ }
+ `;
+
+ t.expectCompileResult(scope === undefined || t.params.attribute[scope], code);
+});
+
+g.test('multi_align').
+desc('Tests that align multiple times is an error').
+params((u) => u.combine('multi', [true, false])).
+fn((t) => {
+ let code = `struct A {
+ @align(128) `;
+
+ if (t.params.multi === true) {
+ code += '@align(128) ';
+ }
+
+ code += `a : i32,
+ }
+
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4(1., 1., 1., 1.);
+ }`;
+
+ t.expectCompileResult(!t.params.multi, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/attribute.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/attribute.spec.js
new file mode 100644
index 0000000000..3d8a1ba9c9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/attribute.spec.js
@@ -0,0 +1,87 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for attributes`;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 kPossibleValues = {
+ val: '32',
+ expr: '30 + 2',
+ override: 'a_override',
+ user_func: 'a_func()',
+ const_func: 'min(4, 8)',
+ const: 'a_const'
+};
+const kAttributeUsage = {
+ align: '@align($val)',
+ binding: '@binding($val) @group(0)',
+ group: '@binding(1) @group($val)',
+ id: '@id($val)',
+ location: '@location($val)',
+ size: '@size($val)',
+ workgroup_size: '@workgroup_size($val, $val, $val)'
+};
+const kAllowedUsages = {
+ align: ['val', 'expr', 'const', 'const_func'],
+ binding: ['val', 'expr', 'const', 'const_func'],
+ group: ['val', 'expr', 'const', 'const_func'],
+ id: ['val', 'expr', 'const', 'const_func'],
+ location: ['val', 'expr', 'const', 'const_func'],
+ size: ['val', 'expr', 'const', 'const_func'],
+ workgroup_size: ['val', 'expr', 'const', 'const_func', 'override']
+};
+
+g.test('expressions').
+desc(`Tests attributes which allow expressions`).
+params((u) =>
+u.combine('value', keysOf(kPossibleValues)).combine('attribute', keysOf(kAllowedUsages))
+).
+fn((t) => {
+ const attributes = {
+ align: '',
+ binding: '@binding(0) @group(0)',
+ group: '@binding(1) @group(1)',
+ id: '@id(2)',
+ location: '@location(0)',
+ size: '',
+ workgroup_size: '@workgroup_size(1)'
+ };
+
+ const val = kPossibleValues[t.params.value];
+ attributes[t.params.attribute] = kAttributeUsage[t.params.attribute].replace(/(\$val)/g, val);
+
+ const code = `
+fn a_func() -> i32 {
+ return 4;
+}
+
+const a_const = -2 + 10;
+override a_override: i32 = 2;
+
+${attributes.id} override my_id: i32 = 4;
+
+struct B {
+ ${attributes.align} ${attributes.size} a: i32,
+}
+
+${attributes.binding}
+var<uniform> uniform_buffer_1: B;
+
+${attributes.group}
+var<uniform> uniform_buffer_2: B;
+
+@fragment
+fn main() -> ${attributes.location} vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+}
+
+@compute
+${attributes.workgroup_size}
+fn compute_main() {}
+`;
+
+ const pass = kAllowedUsages[t.params.attribute].includes(t.params.value);
+ t.expectCompileResult(pass, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/binary_ops.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/binary_ops.spec.js
new file mode 100644
index 0000000000..d605a76293
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/binary_ops.spec.js
@@ -0,0 +1,89 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for binary ops`;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 = {
+ and_bool_literal_bool_literal: {
+ src: `let a = true & true;`,
+ pass: true
+ },
+ and_bool_expr_bool_expr: {
+ src: `let a = (1 == 2) & (3 == 4);`,
+ pass: true
+ },
+ and_bool_literal_bool_expr: {
+ src: `let a = true & (1 == 2);`,
+ pass: true
+ },
+ and_bool_expr_bool_literal: {
+ src: `let a = (1 == 2) & true;`,
+ pass: true
+ },
+ and_bool_literal_int_literal: {
+ src: `let a = true & 1;`,
+ pass: false
+ },
+ and_int_literal_bool_literal: {
+ src: `let a = 1 & true;`,
+ pass: false
+ },
+ and_bool_expr_int_literal: {
+ src: `let a = (1 == 2) & 1;`,
+ pass: false
+ },
+ and_int_literal_bool_expr: {
+ src: `let a = 1 & (1 == 2);`,
+ pass: false
+ },
+
+ or_bool_literal_bool_literal: {
+ src: `let a = true | true;`,
+ pass: true
+ },
+ or_bool_expr_bool_expr: {
+ src: `let a = (1 == 2) | (3 == 4);`,
+ pass: true
+ },
+ or_bool_literal_bool_expr: {
+ src: `let a = true | (1 == 2);`,
+ pass: true
+ },
+ or_bool_expr_bool_literal: {
+ src: `let a = (1 == 2) | true;`,
+ pass: true
+ },
+ or_bool_literal_int_literal: {
+ src: `let a = true | 1;`,
+ pass: false
+ },
+ or_int_literal_bool_literal: {
+ src: `let a = 1 | true;`,
+ pass: false
+ },
+ or_bool_expr_int_literal: {
+ src: `let a = (1 == 2) | 1;`,
+ pass: false
+ },
+ or_int_literal_bool_expr: {
+ src: `let a = 1 | (1 == 2);`,
+ pass: false
+ }
+};
+
+g.test('all').
+desc('Test that binary operators are 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/blankspace.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/blankspace.spec.js
new file mode 100644
index 0000000000..e5e07597ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/blankspace.spec.js
@@ -0,0 +1,65 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for blankspace handling`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('null_characters').
+desc(`Test that WGSL source containing a null character is rejected.`).
+params((u) =>
+u.
+combine('contains_null', [true, false]).
+combine('placement', ['comment', 'delimiter', 'eol']).
+beginSubcases()
+).
+fn((t) => {
+ let code = '';
+ if (t.params.placement === 'comment') {
+ code = `// Here is a ${t.params.contains_null ? '\0' : 'Z'} character`;
+ } else if (t.params.placement === 'delimiter') {
+ code = `const${t.params.contains_null ? '\0' : ' '}name : i32 = 0;`;
+ } else if (t.params.placement === 'eol') {
+ code = `const name : i32 = 0;${t.params.contains_null ? '\0' : ''}`;
+ }
+ t.expectCompileResult(!t.params.contains_null, code);
+});
+
+g.test('blankspace').
+desc(`Test that all blankspace characters act as delimiters.`).
+params((u) =>
+u.
+combine('blankspace', [
+['\u0020', 'space'],
+['\u0009', 'horizontal_tab'],
+['\u000a', 'line_feed'],
+['\u000b', 'vertical_tab'],
+['\u000c', 'form_feed'],
+['\u000d', 'carriage_return'],
+['\u0085', 'next_line'],
+['\u200e', 'left_to_right_mark'],
+['\u200f', 'right_to_left_mark'],
+['\u2028', 'line_separator'],
+['\u2029', 'paragraph_separator']]
+).
+beginSubcases()
+).
+fn((t) => {
+ const code = `const${t.params.blankspace[0]}ident : i32 = 0;`;
+ t.expectCompileResult(true, code);
+});
+
+g.test('bom').
+desc(
+ `Tests that including a BOM causes a shader compile error.
+
+Note, per RFC 2632, for protocols which forbit the use of U+FEFF then the BOM is treated as a
+"ZERO WIDTH NO-BREAK SPACE". The "ZERO WIDTH NO-BREAK SPACE" is not a valid WGSL blankspace code
+point, so the BOM ends up as a shader compilation error.
+ `
+).
+params((u) => u.combine('include_bom', [true, false])).
+fn((t) => {
+ const code = `${t.params.include_bom ? '\uFEFF' : ''}const name : i32 = 0;`;
+ t.expectCompileResult(!t.params.include_bom, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/break.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/break.spec.js
new file mode 100644
index 0000000000..bfdb0fc279
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/break.spec.js
@@ -0,0 +1,84 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for break`;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 = {
+ loop_break: {
+ src: 'loop { break; }',
+ pass: true
+ },
+ loop_if_break: {
+ 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
+ },
+ while_if_break: {
+ src: 'while true { if true { break; } }',
+ pass: true
+ },
+ for_break: {
+ src: 'for (;;) { break; }',
+ pass: true
+ },
+ for_if_break: {
+ src: 'for (;;) { if true { break; } }',
+ pass: true
+ },
+ switch_case_break: {
+ src: 'switch(1) { default: { break; } }',
+ pass: true
+ },
+ switch_case_if_break: {
+ src: 'switch(1) { default: { if true { break; } } }',
+ pass: true
+ },
+ break: {
+ src: 'break;',
+ pass: false
+ },
+ return_break: {
+ src: 'return break;',
+ pass: false
+ },
+ if_break: {
+ src: 'if true { break; }',
+ pass: false
+ },
+ continuing_break: {
+ src: 'loop { continuing { break; } }',
+ pass: false
+ },
+ continuing_if_break: {
+ src: 'loop { continuing { if (true) { break; } } }',
+ pass: false
+ },
+ switch_break: {
+ src: 'switch(1) { break; }',
+ pass: false
+ }
+};
+
+g.test('placement').
+desc('Test that break 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/builtin.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/builtin.spec.js
new file mode 100644
index 0000000000..4d6dc7652b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/builtin.spec.js
@@ -0,0 +1,144 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for @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 kTests = {
+ pos: {
+ src: `@builtin(position)`,
+ pass: true
+ },
+ trailing_comma: {
+ src: `@builtin(position,)`,
+ pass: true
+ },
+ newline_in_attr: {
+ src: `@ \n builtin(position)`,
+ pass: true
+ },
+ whitespace_in_attr: {
+ src: `@/* comment */builtin/* comment */\n\n(\t/*comment*/position/*comment*/)`,
+ pass: true
+ },
+ invalid_name: {
+ src: `@abuiltin(position)`,
+ pass: false
+ },
+ no_params: {
+ src: `@builtin`,
+ pass: false
+ },
+ missing_param: {
+ src: `@builtin()`,
+ pass: false
+ },
+ missing_parens: {
+ src: `@builtin position`,
+ pass: false
+ },
+ missing_lparen: {
+ src: `@builtin position)`,
+ pass: false
+ },
+ missing_rparen: {
+ src: `@builtin(position`,
+ pass: false
+ },
+ multiple_params: {
+ src: `@builtin(position, frag_depth)`,
+ pass: false
+ },
+ ident_param: {
+ src: `@builtin(identifier)`,
+ pass: false
+ },
+ number_param: {
+ src: `@builtin(2)`,
+ pass: false
+ }
+};
+
+g.test('parse').
+desc(`Test that @builtin is parsed correctly.`).
+params((u) => u.combine('builtin', keysOf(kTests))).
+fn((t) => {
+ const src = kTests[t.params.builtin].src;
+ const code = `
+@vertex
+fn main() -> ${src} vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.builtin].pass, code);
+});
+
+g.test('placement').
+desc('Tests the locations @builtin is allowed to appear').
+params((u) =>
+u.
+combine('scope', [
+// The fn-param and fn-ret are part of the shader_io/builtins tests
+'private-var',
+'storage-var',
+'struct-member',
+'non-ep-param',
+'non-ep-ret',
+'fn-decl',
+'fn-var',
+'while-stmt',
+undefined]
+).
+combine('attribute', [
+{
+ 'private-var': false,
+ 'storage-var': false,
+ 'struct-member': true,
+ 'non-ep-param': false,
+ 'non-ep-ret': false,
+ 'fn-decl': false,
+ 'fn-var': false,
+ 'fn-return': false,
+ 'while-stmt': false
+}]
+).
+beginSubcases()
+).
+fn((t) => {
+ const scope = t.params.scope;
+
+ const attr = '@builtin(vertex_index)';
+ const code = `
+ ${scope === 'private-var' ? attr : ''}
+ var<private> priv_var : u32;
+
+ ${scope === 'storage-var' ? attr : ''}
+ @group(0) @binding(0)
+ var<storage> stor_var : u32;
+
+ struct A {
+ ${scope === 'struct-member' ? attr : ''}
+ a : u32,
+ }
+
+ fn v(${scope === 'non-ep-param' ? attr : ''} i : u32) ->
+ ${scope === 'non-ep-ret' ? attr : ''} u32 { return 1; }
+
+ @vertex
+ ${scope === 'fn-decl' ? attr : ''}
+ fn f(
+ @location(0) b : u32,
+ ) -> @builtin(position) vec4f {
+ ${scope === 'fn-var' ? attr : ''}
+ var<function> func_v : u32;
+
+ ${scope === 'while-stmt' ? attr : ''}
+ while false {}
+
+ return vec4(1, 1, 1, 1);
+ }
+ `;
+
+ t.expectCompileResult(scope === undefined || t.params.attribute[scope], code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/comments.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/comments.spec.js
new file mode 100644
index 0000000000..9d23bbe202
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/comments.spec.js
@@ -0,0 +1,75 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for comments`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('comments').
+desc(`Test that valid comments are handled correctly, including nesting.`).
+fn((t) => {
+ const code = `
+/**
+ * Here is my shader.
+ *
+ * /* I can nest /**/ comments. */
+ * // I can nest line comments too.
+ **/
+@fragment // This is the stage
+fn main(/*
+no
+parameters
+*/) -> @location(0) vec4<f32> {
+ return/*block_comments_delimit_tokens*/vec4<f32>(.4, .2, .3, .1);
+}/* terminated block comments are OK at EOF...*/`;
+ t.expectCompileResult(true, code);
+});
+
+g.test('line_comment_eof').
+desc(`Test that line comments can come at EOF.`).
+fn((t) => {
+ const code = `
+@fragment
+fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+}
+// line comments are OK at EOF...`;
+ t.expectCompileResult(true, code);
+});
+
+g.test('line_comment_terminators').
+desc(`Test that line comments are terminated by any blankspace other than space and \t`).
+params((u) =>
+u.
+combine('blankspace', [
+[' ', 'space'],
+['\t', 'tab'],
+['\u000a', 'line_feed'],
+['\u000b', 'vertical_tab'],
+['\u000c', 'form_feed'],
+['\u000d', 'carriage_return'],
+['\u000d\u000a', 'carriage_return_line_feed'],
+['\u0085', 'next_line'],
+['\u2028', 'line_separator'],
+['\u2029', 'paragraph_separator']]
+).
+beginSubcases()
+).
+fn((t) => {
+ const code = `// Line comment${t.params.blankspace[0]}const invalid_outside_comment = should_fail`;
+
+ t.expectCompileResult([' ', '\t'].includes(t.params.blankspace[0]), code);
+});
+
+g.test('unterminated_block_comment').
+desc(`Test that unterminated block comments cause an error`).
+params((u) => u.combine('terminated', [true, false]).beginSubcases()).
+fn((t) => {
+ const code = `
+/**
+ * Unterminated block comment.
+ *
+ ${t.params.terminated ? '*/' : ''}`;
+
+ t.expectCompileResult(t.params.terminated, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const.spec.js
new file mode 100644
index 0000000000..fcc431a82a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const.spec.js
@@ -0,0 +1,57 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for @const`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('placement').
+desc('Tests @const is not allowed to appear').
+params((u) =>
+u.combine('scope', [
+'private-var',
+'storage-var',
+'struct-member',
+'fn-decl',
+'fn-param',
+'fn-var',
+'fn-return',
+'while-stmt',
+undefined]
+)
+).
+fn((t) => {
+ const scope = t.params.scope;
+
+ const attr = '@const';
+ const code = `
+ ${scope === 'private-var' ? attr : ''}
+ var<private> priv_var : i32;
+
+ ${scope === 'storage-var' ? attr : ''}
+ @group(0) @binding(0)
+ var<storage> stor_var : i32;
+
+ struct A {
+ ${scope === 'struct-member' ? attr : ''}
+ a : i32,
+ }
+
+ @vertex
+ ${scope === 'fn-decl' ? attr : ''}
+ fn f(
+ ${scope === 'fn-param' ? attr : ''}
+ @location(0) b : i32,
+ ) -> ${scope === 'fn-return' ? attr : ''} @builtin(position) vec4f {
+ ${scope === 'fn-var' ? attr : ''}
+ var<function> func_v : i32;
+
+ ${scope === 'while-stmt' ? attr : ''}
+ while false {}
+
+ return vec4(1, 1, 1, 1);
+ }
+ `;
+
+ t.expectCompileResult(scope === undefined, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const_assert.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const_assert.spec.js
new file mode 100644
index 0000000000..ba19057ecc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/const_assert.spec.js
@@ -0,0 +1,38 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Parser validation tests for const_assert`;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 kCases = {
+ no_parentheses: { code: `const_assert true;`, pass: true },
+ left_parenthesis_only: { code: `const_assert(true;`, pass: false },
+ right_parenthesis_only: { code: `const_assert true);`, pass: false },
+ both_parentheses: { code: `const_assert(true);`, pass: true },
+ condition_on_newline: {
+ code: `const_assert
+true;`,
+ pass: true
+ },
+ multiline_with_parentheses: {
+ code: `const_assert
+(
+ true
+);`,
+ pass: true
+ },
+ invalid_expression: { code: `const_assert(1!2);`, pass: false },
+ no_condition_no_parentheses: { code: `const_assert;`, pass: false },
+ no_condition_with_parentheses: { code: `const_assert();`, pass: false },
+ not_a_boolean: { code: `const_assert 42;`, pass: false }
+};
+
+g.test('parse').
+desc(`Tests that the const_assert statement parses correctly.`).
+params((u) => u.combine('case', keysOf(kCases))).
+fn((t) => {
+ const c = kCases[t.params.case];
+ t.expectCompileResult(c.pass, c.code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/diagnostic.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/diagnostic.spec.js
new file mode 100644
index 0000000000..8c05c6607f
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/diagnostic.spec.js
@@ -0,0 +1,201 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for diagnostic directive and attribute`;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 kSpecDiagnosticRules = ['derivative_uniformity'];
+const kSpecDiagnosticSeverities = ['off', 'info', 'warning', 'error'];
+const kDiagnosticTypes = ['attribute', 'directive'];
+
+const kBadSeverities = ['none', 'warn', 'goose', 'fatal', 'severe'];
+const kBadSingleTokenRules = ['unknown', 'blahblahblah', 'derivative_uniform'];
+
+function generateDiagnostic(type, severity, rule) {
+ const diagnostic = `diagnostic(${severity}, ${rule})`;
+ if (type === 'directive') {
+ return diagnostic;
+ } else {
+ return '@' + diagnostic;
+ }
+}
+
+const kValidLocations = {
+ module: (diag) => `${diag};`,
+ function: (diag) => `${diag} fn foo() { }`,
+ compound: (diag) => `fn foo() { ${diag} { } }`,
+ if_stmt: (diag) => `fn foo() { ${diag} if true { } }`,
+ if_then: (diag) => `fn foo() { if true ${diag} { } }`,
+ if_else: (diag) => `fn foo() { if true { } else ${diag} { } }`,
+ switch_stmt: (diag) => `fn foo() { ${diag} switch 0 { default { } } }`,
+ switch_body: (diag) => `fn foo() { switch 0 ${diag} { default { } } }`,
+ switch_default: (diag) => `fn foo() { switch 0 { default ${diag} { } } }`,
+ switch_case: (diag) => `fn foo() { switch 0 { case 0 ${diag} { } default { } } }`,
+ loop_stmt: (diag) => `fn foo() { ${diag} loop { break; } }`,
+ loop_body: (diag) => `fn foo() { loop ${diag} { break; } }`,
+ loop_continuing: (diag) => `fn foo() { loop { continuing ${diag} { break if true; } } }`,
+ while_stmt: (diag) => `fn foo() { ${diag} while true { break; } }`,
+ while_body: (diag) => `fn foo() { while true ${diag} { break; } }`,
+ for_stmt: (diag) => `fn foo() { ${diag} for (var i = 0; i < 10; i++) { } }`,
+ for_body: (diag) => `fn foo() { for (var i = 0; i < 10; i++) ${diag} { } }`
+};
+
+const kInvalidLocations = {
+ module_var: (diag) => `${diag} var<private> x : u32;`,
+ module_const: (diag) => `${diag} const x = 0;`,
+ module_override: (diag) => `${diag} override x : u32;`,
+ struct: (diag) => `${diag} struct S { x : u32 }`,
+ struct_member: (diag) => ` struct S { ${diag} x : u32 }`,
+ function_params: (diag) => `fn foo${diag}() { }`,
+ function_var: (diag) => `fn foo() { ${diag} var x = 0; }`,
+ function_let: (diag) => `fn foo() { ${diag} let x = 0; }`,
+ function_const: (diag) => `fn foo() { ${diag} const x = 0; }`,
+ pre_else: (diag) => `fn foo() { if true { } ${diag} else { } }`,
+ pre_default: (diag) => `fn foo() { switch 0 { ${diag} default { } } }`,
+ pre_case: (diag) => `fn foo() { switch 0 { ${diag} case 0 { } default { } } }`,
+ pre_continuing: (diag) => `fn foo() { loop { ${diag} continuing { break if true; } } }`,
+ pre_for_params: (diag) => `fn foo() { for ${diag} (var i = 0; i < 10; i++) { } }`
+};
+
+const kNestedLocations = {
+ compound: (d1, d2) => `${d1} fn foo() { ${d2} { } }`,
+ if_stmt: (d1, d2) => `fn foo() { ${d1} if true ${d2} { } }`,
+ switch_stmt: (d1, d2) => `fn foo() { ${d1} switch 0 ${d2} { default { } } }`,
+ switch_body: (d1, d2) => `fn foo() { switch 0 ${d1} { default ${d2} { } } }`,
+ switch_case: (d1, d2) =>
+ `fn foo() { switch 0 { case 0 ${d1} { } default ${d2} { } } }`,
+ loop_stmt: (d1, d2) => `fn foo() { ${d1} loop ${d2} { break; } }`,
+ while_stmt: (d1, d2) => `fn foo() { ${d1} while true ${d2} { break; } }`,
+ for_stmt: (d1, d2) => `fn foo() { ${d1} for (var i = 0; i < 10; i++) ${d2} { } }`
+};
+
+g.test('valid_params').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests required accepted diagnostic parameters`).
+params((u) =>
+u.
+combine('severity', kSpecDiagnosticSeverities).
+combine('rule', kSpecDiagnosticRules).
+combine('type', kDiagnosticTypes)
+).
+fn((t) => {
+ const diag = generateDiagnostic(t.params.type, t.params.severity, t.params.rule);
+ let code = ``;
+ if (t.params.type === 'directive') {
+ code = kValidLocations['module'](diag);
+ } else {
+ code = kValidLocations['function'](diag);
+ }
+ t.expectCompileResult(true, code);
+});
+
+g.test('invalid_severity').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests invalid severities are rejected`).
+params((u) => u.combine('severity', kBadSeverities).combine('type', kDiagnosticTypes)).
+fn((t) => {
+ const diag = generateDiagnostic(t.params.type, t.params.severity, 'derivative_uniformity');
+ let code = ``;
+ if (t.params.type === 'directive') {
+ code = kValidLocations['module'](diag);
+ } else {
+ code = kValidLocations['function'](diag);
+ }
+ t.expectCompileResult(false, code);
+});
+
+g.test('warning_unknown_rule').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests unknown single token rules issue a warning`).
+params((u) => u.combine('type', kDiagnosticTypes).combine('rule', kBadSingleTokenRules)).
+fn((t) => {
+ const diag = generateDiagnostic(t.params.type, 'info', t.params.rule);
+ let code = ``;
+ if (t.params.type === 'directive') {
+ code = kValidLocations['module'](diag);
+ } else {
+ code = kValidLocations['function'](diag);
+ }
+ t.expectCompileWarning(true, code);
+});
+
+g.test('valid_locations').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests valid locations`).
+params((u) => u.combine('type', kDiagnosticTypes).combine('location', keysOf(kValidLocations))).
+fn((t) => {
+ const diag = generateDiagnostic(t.params.type, 'info', 'derivative_uniformity');
+ const code = kValidLocations[t.params.location](diag);
+ let res = true;
+ if (t.params.type === 'directive') {
+ res = t.params.location === 'module';
+ } else {
+ res = t.params.location !== 'module';
+ }
+ if (res === false) {
+ t.expectCompileResult(true, kValidLocations[t.params.location](''));
+ }
+ t.expectCompileResult(res, code);
+});
+
+g.test('invalid_locations').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests invalid locations`).
+params((u) => u.combine('type', kDiagnosticTypes).combine('location', keysOf(kInvalidLocations))).
+fn((t) => {
+ const diag = generateDiagnostic(t.params.type, 'info', 'derivative_uniformity');
+ t.expectCompileResult(true, kInvalidLocations[t.params.location](''));
+ t.expectCompileResult(false, kInvalidLocations[t.params.location](diag));
+});
+
+g.test('conflicting_directive').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests conflicts between directives`).
+params((u) => u.combine('s1', kSpecDiagnosticSeverities).combine('s2', kSpecDiagnosticSeverities)).
+fn((t) => {
+ const d1 = generateDiagnostic('directive', t.params.s1, 'derivative_uniformity');
+ const d2 = generateDiagnostic('directive', t.params.s2, 'derivative_uniformity');
+ const code = `${kValidLocations['module'](d1)}\n${kValidLocations['module'](d2)}`;
+ t.expectCompileResult(t.params.s1 === t.params.s2, code);
+});
+
+g.test('conflicting_attribute_same_location').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests conflicts between attributes`).
+params((u) =>
+u.
+combine('loc', keysOf(kValidLocations)).
+combine('s1', kSpecDiagnosticSeverities).
+combine('s2', kSpecDiagnosticSeverities).
+filter((u) => {
+ return u.loc !== 'module';
+})
+).
+fn((t) => {
+ const d1 = generateDiagnostic('attribute', t.params.s1, 'derivative_uniformity');
+ const d2 = generateDiagnostic('attribute', t.params.s2, 'derivative_uniformity');
+ const diag = d1 + ' ' + d2;
+ const code = `${kValidLocations[t.params.loc](diag)}`;
+ t.expectCompileResult(t.params.s1 === t.params.s2, code);
+});
+
+g.test('conflicting_attribute_different_location').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics').
+desc(`Tests conflicts between attributes`).
+params((u) =>
+u.
+combine('loc', keysOf(kNestedLocations)).
+combine('s1', kSpecDiagnosticSeverities).
+combine('s2', kSpecDiagnosticSeverities).
+filter((u) => {
+ return u.s1 !== u.s2;
+})
+).
+fn((t) => {
+ const d1 = generateDiagnostic('attribute', t.params.s1, 'derivative_uniformity');
+ const d2 = generateDiagnostic('attribute', t.params.s2, 'derivative_uniformity');
+ const code = `${kNestedLocations[t.params.loc](d1, d2)}`;
+ t.expectCompileResult(true, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/discard.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/discard.spec.js
new file mode 100644
index 0000000000..d7e76f6bc6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/discard.spec.js
@@ -0,0 +1,65 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for discard`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('placement').
+desc('Test that discard usage is validated').
+params((u) =>
+u.combine('place', ['compute', 'vertex', 'fragment', 'module', 'subfrag', 'subvert', 'subcomp'])
+).
+fn((t) => {
+ const pos = {
+ module: '',
+ subvert: '',
+ subfrag: '',
+ subcomp: '',
+ vertex: '',
+ fragment: '',
+ compute: ''
+ };
+
+ pos[t.params.place] = 'discard;';
+
+ const code = `
+${pos.module}
+
+fn subvert() {
+ ${pos.subvert}
+}
+
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${pos.vertex}
+ subvert();
+ return vec4f(1);
+}
+
+fn subfrag() {
+ ${pos.subfrag}
+}
+
+@fragment
+fn frag() -> @location(0) vec4f {
+ ${pos.fragment}
+ subfrag();
+ return vec4f(1);
+}
+
+fn subcomp() {
+ ${pos.subcomp}
+}
+
+@compute
+@workgroup_size(1)
+fn comp() {
+ ${pos.compute}
+ subcomp();
+}
+`;
+
+ const pass = ['fragment', 'subfrag'].includes(t.params.place);
+ t.expectCompileResult(pass, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/enable.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/enable.spec.js
new file mode 100644
index 0000000000..a95ea54acf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/enable.spec.js
@@ -0,0 +1,70 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Parser validation tests for enable`;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 kCases = {
+ f16: { code: `enable f16;`, pass: true },
+ decl_before: {
+ code: `alias i = i32;
+enable f16;`,
+ pass: false
+ },
+ after_decl: {
+ code: `enable f16;
+alias i = i32;`,
+ pass: true
+ },
+ const_assert_before: {
+ code: `const_assert 1 == 1;
+enable f16;`,
+ pass: false
+ },
+ const_assert_after: {
+ code: `enable f16;
+const_assert 1 == 1;`,
+ pass: true
+ },
+ embedded_comment: {
+ code: `/* comment
+
+*/enable f16;`,
+ pass: true
+ },
+ parens: {
+ code: `enable(f16);`,
+ pass: false
+ },
+ multi_line: {
+ code: `enable
+f16;`,
+ pass: true
+ },
+ multiple_enables: {
+ code: `enable f16;
+enable f16;`,
+ pass: true
+ },
+ multipe_entries: {
+ code: `enable f16, f16, f16;`,
+ pass: true
+ },
+ unknown: {
+ code: `enable unknown;`,
+ pass: false
+ }
+};
+
+g.test('enable').
+desc(`Tests that enables are validated correctly`).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+params((u) => u.combine('case', keysOf(kCases))).
+fn((t) => {
+ const c = kCases[t.params.case];
+ t.expectCompileResult(c.pass, c.code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/identifiers.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/identifiers.spec.js
new file mode 100644
index 0000000000..d7510cc5b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/identifiers.spec.js
@@ -0,0 +1,407 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for identifiers`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidIdentifiers = new Set([
+'foo',
+'Foo',
+'FOO',
+'_0',
+'_foo0',
+'_0foo',
+'foo__0',
+'Δέλτα',
+'réflexion',
+'Кызыл',
+'𐰓𐰏𐰇',
+'朝焼け',
+'سلام',
+'검정',
+'שָׁלוֹם',
+'गुलाबी',
+'փիրուզ',
+// Builtin type identifiers:
+'array',
+'atomic',
+'bool',
+'bf16',
+'bitcast',
+'f32',
+'f16',
+'f64',
+'i32',
+'i16',
+'i64',
+'i8',
+'mat2x2',
+'mat2x3',
+'mat2x4',
+'mat3x2',
+'mat3x3',
+'mat3x4',
+'mat4x2',
+'mat4x3',
+'mat4x4',
+'ptr',
+'quat',
+'sampler',
+'sampler_comparison',
+'signed',
+'texture_1d',
+'texture_2d',
+'texture_2d_array',
+'texture_3d',
+'texture_cube',
+'texture_cube_array',
+'texture_multisampled_2d',
+'texture_storage_1d',
+'texture_storage_2d',
+'texture_storage_2d_array',
+'texture_storage_3d',
+'texture_depth_2d',
+'texture_depth_2d_array',
+'texture_depth_cube',
+'texture_depth_cube_array',
+'texture_depth_multisampled_2d',
+'u32',
+'u16',
+'u64',
+'u8',
+'unsigned',
+'vec2',
+'vec3',
+'vec4']
+);
+const kInvalidIdentifiers = new Set([
+'_', // Single underscore is a syntactic token for phony assignment.
+'__', // Leading double underscore is reserved.
+'__foo', // Leading double underscore is reserved.
+'0foo', // Must start with single underscore or a letter.
+// No punctuation:
+'foo.bar',
+'foo-bar',
+'foo+bar',
+'foo#bar',
+'foo!bar',
+'foo\\bar',
+'foo/bar',
+'foo,bar',
+'foo@bar',
+'foo::bar',
+// Keywords:
+'alias',
+'break',
+'case',
+'const',
+'const_assert',
+'continue',
+'continuing',
+'default',
+'diagnostic',
+'discard',
+'else',
+'enable',
+'false',
+'fn',
+'for',
+'if',
+'let',
+'loop',
+'override',
+'requires',
+'return',
+'struct',
+'switch',
+'true',
+'var',
+'while',
+// Reserved Words
+'NULL',
+'Self',
+'abstract',
+'active',
+'alignas',
+'alignof',
+'as',
+'asm',
+'asm_fragment',
+'async',
+'attribute',
+'auto',
+'await',
+'become',
+'binding_array',
+'cast',
+'catch',
+'class',
+'co_await',
+'co_return',
+'co_yield',
+'coherent',
+'column_major',
+'common',
+'compile',
+'compile_fragment',
+'concept',
+'const_cast',
+'consteval',
+'constexpr',
+'constinit',
+'crate',
+'debugger',
+'decltype',
+'delete',
+'demote',
+'demote_to_helper',
+'do',
+'dynamic_cast',
+'enum',
+'explicit',
+'export',
+'extends',
+'extern',
+'external',
+'fallthrough',
+'filter',
+'final',
+'finally',
+'friend',
+'from',
+'fxgroup',
+'get',
+'goto',
+'groupshared',
+'highp',
+'impl',
+'implements',
+'import',
+'inline',
+'instanceof',
+'interface',
+'layout',
+'lowp',
+'macro',
+'macro_rules',
+'match',
+'mediump',
+'meta',
+'mod',
+'module',
+'move',
+'mut',
+'mutable',
+'namespace',
+'new',
+'nil',
+'noexcept',
+'noinline',
+'nointerpolation',
+'noperspective',
+'null',
+'nullptr',
+'of',
+'operator',
+'package',
+'packoffset',
+'partition',
+'pass',
+'patch',
+'pixelfragment',
+'precise',
+'precision',
+'premerge',
+'priv',
+'protected',
+'pub',
+'public',
+'readonly',
+'ref',
+'regardless',
+'register',
+'reinterpret_cast',
+'require',
+'resource',
+'restrict',
+'self',
+'set',
+'shared',
+'sizeof',
+'smooth',
+'snorm',
+'static',
+'static_assert',
+'static_cast',
+'std',
+'subroutine',
+'super',
+'target',
+'template',
+'this',
+'thread_local',
+'throw',
+'trait',
+'try',
+'type',
+'typedef',
+'typeid',
+'typename',
+'typeof',
+'union',
+'unless',
+'unorm',
+'unsafe',
+'unsized',
+'use',
+'using',
+'varying',
+'virtual',
+'volatile',
+'wgsl',
+'where',
+'with',
+'writeonly',
+'yield']
+);
+
+g.test('module_var_name').
+desc(
+ `Test that valid identifiers are accepted for names of module-scope 'var's, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const type = t.params.ident === 'i32' ? 'u32' : 'i32';
+ const code = `var<private> ${t.params.ident} : ${type};`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('module_const_name').
+desc(
+ `Test that valid identifiers are accepted for names of module-scope 'const's, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const type = t.params.ident === 'i32' ? 'u32' : 'i32';
+ const code = `const ${t.params.ident} : ${type} = 0;`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('override_name').
+desc(
+ `Test that valid identifiers are accepted for names of 'override's, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const type = t.params.ident === 'i32' ? 'u32' : 'i32';
+ const code = `override ${t.params.ident} : ${type} = 0;`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('function_name').
+desc(
+ `Test that valid identifiers are accepted for names of functions, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const code = `fn ${t.params.ident}() {}`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('struct_name').
+desc(
+ `Test that valid identifiers are accepted for names of structs, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const type = t.params.ident === 'i32' ? 'u32' : 'i32';
+ const code = `struct ${t.params.ident} { i : ${type} }`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('alias_name').
+desc(
+ `Test that valid identifiers are accepted for names of aliases, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const type = t.params.ident === 'i32' ? 'u32' : 'i32';
+ const code = `alias ${t.params.ident} = ${type};`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('function_param_name').
+desc(
+ `Test that valid identifiers are accepted for names of function parameters, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const type = t.params.ident === 'i32' ? 'u32' : 'i32';
+ const code = `fn F(${t.params.ident} : ${type}) {}`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('function_const_name').
+desc(
+ `Test that valid identifiers are accepted for names of function-scoped 'const's, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const code = `fn F() {
+ const ${t.params.ident} = 1;
+}`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('function_let_name').
+desc(
+ `Test that valid identifiers are accepted for names of function-scoped 'let's, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const code = `fn F() {
+ let ${t.params.ident} = 1;
+}`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('function_var_name').
+desc(
+ `Test that valid identifiers are accepted for names of function-scoped 'var's, and invalid identifiers are rejected.`
+).
+params((u) =>
+u.combine('ident', new Set([...kValidIdentifiers, ...kInvalidIdentifiers])).beginSubcases()
+).
+fn((t) => {
+ const code = `fn F() {
+ var ${t.params.ident} = 1;
+}`;
+ t.expectCompileResult(kValidIdentifiers.has(t.params.ident), code);
+});
+
+g.test('non_normalized').
+desc(`Test that identifiers are not unicode normalized`).
+fn((t) => {
+ const code = `var<private> \u212b : i32; // \u212b normalizes with NFC to \u00c5
+var<private> \u00c5 : i32;`;
+ t.expectCompileResult(true, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/literal.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/literal.spec.js
new file mode 100644
index 0000000000..de1dde69c7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/literal.spec.js
@@ -0,0 +1,302 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for literals`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('bools').
+desc(`Test that valid bools are accepted.`).
+params((u) => u.combine('val', ['true', 'false']).beginSubcases()).
+fn((t) => {
+ const code = `var test = ${t.params.val};`;
+ t.expectCompileResult(true, t.wrapInEntryPoint(code));
+});
+
+const kAbstractIntNonNegative = new Set([
+'0x123', // hex number
+'123', // signed number, no suffix
+'0', // zero
+'0x3f', // hex with 'f' as last character
+'2147483647' // max signed int
+]);
+
+const kAbstractIntNegative = new Set([
+'-0x123', // hex number
+'-123', // signed number, no suffix
+'-0x3f', // hex with 'f' as last character
+'-2147483647', // nagative of max signed int
+'-2147483648' // min signed int
+]);
+
+const kI32 = new Set([
+'94i', // signed number
+'2147483647i', // max signed int
+'-2147483647i', // min parsable signed int
+'i32(-2147483648)' // min signed int
+]);
+
+const kU32 = new Set([
+'42u', // unsigned number
+'0u', // min unsigned int
+'4294967295u' // max unsigned int
+]);
+
+{
+ const kValidIntegers = new Set([
+ ...kAbstractIntNonNegative,
+ ...kAbstractIntNegative,
+ ...kI32,
+ ...kU32]
+ );
+ const kInvalidIntegers = new Set([
+ '0123', // Integer does not start with zero
+ '2147483648i', // max signed int + 1
+ '-2147483649i', // min signed int - 1
+ '4294967295', // a untyped lhs will be i32, so this is too big
+ '4294967295i', // max unsigned int with i suffix
+ '4294967296u', // max unsigned int + 1
+ '-1u' // negative unsigned
+ ]);
+ g.test('abstract_int').
+ desc(`Test that valid integers are accepted, and invalid integers are rejected.`).
+ params((u) =>
+ u.combine('val', new Set([...kValidIntegers, ...kInvalidIntegers])).beginSubcases()
+ ).
+ fn((t) => {
+ const code = `var test = ${t.params.val};`;
+ t.expectCompileResult(kValidIntegers.has(t.params.val), t.wrapInEntryPoint(code));
+ });
+}
+
+{
+ const kValidI32 = new Set([...kAbstractIntNonNegative, ...kAbstractIntNegative, ...kI32]);
+ const kInvalidI32 = new Set([
+ ...kU32,
+ '2147483648', // max signed int + 1
+ '2147483648i', // max signed int + 1
+ '-2147483649', // min signed int - 1
+ '-2147483649i', // min signed int - 1
+ '1.0', // no conversion from float
+ '1.0f', // no conversion from float
+ '1.0h' // no conversion from float
+ ]);
+ g.test('i32').
+ desc(`Test that valid signed integers are accepted, and invalid signed integers are rejected.`).
+ params((u) => u.combine('val', new Set([...kValidI32, ...kInvalidI32])).beginSubcases()).
+ beforeAllSubcases((t) => {
+ if (t.params.val.includes('h')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ }).
+ fn((t) => {
+ const { val } = t.params;
+ const code = `var test: i32 = ${val};`;
+ const extensionList = val.includes('h') ? ['f16'] : [];
+ t.expectCompileResult(kValidI32.has(val), t.wrapInEntryPoint(code, extensionList));
+ });
+}
+
+{
+ const kValidU32 = new Set([
+ ...kAbstractIntNonNegative,
+ ...kU32,
+ '4294967295' // max unsigned
+ ]);
+ const kInvalidU32 = new Set([
+ ...kAbstractIntNegative,
+ ...kI32,
+ '4294967296', // max unsigned int + 1
+ '4294967296u', // min unsigned int + 1
+ '-1', // min unsigned int - 1
+ '1.0', // no conversion from float
+ '1.0f', // no conversion from float
+ '1.0h' // no conversion from float
+ ]);
+ g.test('u32').
+ desc(
+ `Test that valid unsigned integers are accepted, and invalid unsigned integers are rejected.`
+ ).
+ params((u) => u.combine('val', new Set([...kValidU32, ...kInvalidU32])).beginSubcases()).
+ beforeAllSubcases((t) => {
+ if (t.params.val.includes('h')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ }).
+ fn((t) => {
+ const { val } = t.params;
+ const code = `var test: u32 = ${val};`;
+ const extensionList = val.includes('h') ? ['f16'] : [];
+ t.expectCompileResult(kValidU32.has(val), t.wrapInEntryPoint(code, extensionList));
+ });
+}
+
+const kF32 = new Set([
+'0f', // Zero float
+'0.0f', // Zero float
+'12.223f', // float value
+'12.f', // .f
+'.12f', // No leading number with a f
+'2.4e+4f', // Positive exponent with f suffix
+'2.4e-2f', // Negative exponent with f suffix
+'2.e+4f', // Exponent without decimals
+'1e-4f', // Exponennt without decimal point
+'0x1P+4f' // Hex float no decimal
+]);
+
+const kF16 = new Set([
+'0h', // Zero half
+'1h', // Half no decimal
+'.1h', // Half no leading value
+'1.1e2h', // Exponent half no sign
+'1.1E+2h', // Exponent half, plus (uppercase E)
+'2.4e-2h', // Exponent half, negative
+'0xep2h', // Hexfloat half lower case p
+'0xEp-2h', // Hexfloat uppcase hex value
+'0x3p+2h', // Hex float half positive exponent
+'0x3.2p+2h' // Hex float with decimal half
+]);
+
+const kAbstractFloat = new Set([
+'0.0', // Zero float without suffix
+'.0', // Zero float without leading value
+'12.', // No decimal points
+'00012.', // Leading zeros allowed
+'.12', // No leading digits
+'1.2e2', // Exponent without sign (lowercase e)
+'1.2E2', // Exponent without sign (uppercase e)
+'1.2e+2', // positive exponent
+'2.4e-2', // Negative exponent
+'.1e-2', // Exponent without leading number
+'0x.3', // Hex float, lowercase X
+'0X.3', // Hex float, uppercase X
+'0xa.fp+2', // Hex float, lowercase p
+'0xa.fP+2', // Hex float, uppercase p
+'0xE.fp+2', // Uppercase E (as hex, but matches non hex exponent char)
+'0X1.fp-4' // Hex float negative exponent
+]);
+
+{
+ const kValidFloats = new Set([...kF32, ...kF16, ...kAbstractFloat]);
+ const kInvalidFloats = new Set([
+ '.f', // Must have a number
+ '.e-2', // Exponent without leading values
+ '1.e&2f', // Exponent invalid sign
+ '1.ef', // Exponent without value
+ '1.e+f', // Exponent sign no value
+ '0x.p2', // Hex float no value
+ '0x1p', // Hex float missing exponent
+ '0x1p^', // Hex float invalid exponent
+ '1.0e+999999999999f', // Too big
+ '0x1.0p+999999999999f', // Too big hex
+ '0x1.00000001pf0' // Mantissa too big
+ ]);
+ const kInvalidF16s = new Set([
+ '1.1eh', // Missing exponent value
+ '1.1e!2h', // Invalid exponent sign
+ '1.1e+h', // Missing exponent with sign
+ '1.0e+999999h', // Too large
+ '0x1.0p+999999h', // Too large hex
+ '0xf.h', // Having suffix "h" without "p" or "P"
+ '0x3h' // Having suffix "h" without "p" or "P"
+ ]);
+
+ g.test('abstract_float').
+ desc(`Test that valid floats are accepted, and invalid floats are rejected`).
+ params((u) =>
+ u.
+ combine('val', new Set([...kValidFloats, ...kInvalidFloats, ...kInvalidF16s])).
+ beginSubcases()
+ ).
+ beforeAllSubcases((t) => {
+ if (kF16.has(t.params.val) || kInvalidF16s.has(t.params.val)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ }).
+ fn((t) => {
+ const code = `var test = ${t.params.val};`;
+ const extensionList = kF16.has(t.params.val) || kInvalidF16s.has(t.params.val) ? ['f16'] : [];
+ t.expectCompileResult(
+ kValidFloats.has(t.params.val),
+ t.wrapInEntryPoint(code, extensionList)
+ );
+ });
+}
+
+{
+ const kValidF32 = new Set([
+ ...kF32,
+ ...kAbstractFloat,
+ '1', // AbstractInt
+ '-1' // AbstractInt
+ ]);
+ const kInvalidF32 = new Set([
+ ...kF16, // no conversion
+ '1u', // unsigned
+ '1i', // signed
+ '1h', // half float
+ '.f', // Must have a number
+ '.e-2', // Exponent without leading values
+ '1.e&2f', // Exponent invalid sign
+ '1.ef', // Exponent without value
+ '1.e+f', // Exponent sign no value
+ '0x.p2', // Hex float no value
+ '0x1p', // Hex float missing exponent
+ '0x1p^', // Hex float invalid exponent
+ '1.0e+999999999999f', // Too big
+ '0x1.0p+999999999999f', // Too big hex
+ '0x1.00000001pf0' // Mantissa too big
+ ]);
+
+ g.test('f32').
+ desc(`Test that valid floats are accepted, and invalid floats are rejected`).
+ params((u) => u.combine('val', new Set([...kValidF32, ...kInvalidF32])).beginSubcases()).
+ beforeAllSubcases((t) => {
+ if (kF16.has(t.params.val)) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ }).
+ fn((t) => {
+ const { val } = t.params;
+ const code = `var test: f32 = ${val};`;
+ const extensionList = kF16.has(val) ? ['f16'] : [];
+ t.expectCompileResult(kValidF32.has(val), t.wrapInEntryPoint(code, extensionList));
+ });
+}
+
+{
+ const kValidF16 = new Set([
+ ...kF16,
+ ...kAbstractFloat,
+ '1', // AbstractInt
+ '-1' // AbstractInt
+ ]);
+ const kInvalidF16 = new Set([
+ ...kF32,
+ '1i', // signed int
+ '1u', // unsigned int
+ '1f', // no conversion from f32 to f16
+ '1.1eh', // Missing exponent value
+ '1.1e!2h', // Invalid exponent sign
+ '1.1e+h', // Missing exponent with sign
+ '1.0e+999999h', // Too large
+ '0x1.0p+999999h' // Too large hex
+ ]);
+
+ g.test('f16').
+ desc(
+ `
+Test that valid half floats are accepted, and invalid half floats are rejected
+`
+ ).
+ params((u) => u.combine('val', new Set([...kValidF16, ...kInvalidF16])).beginSubcases()).
+ beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }).
+ fn((t) => {
+ const { val } = t.params;
+ const code = `var test: f16 = ${val};`;
+ const extensionList = ['f16'];
+ t.expectCompileResult(kValidF16.has(val), t.wrapInEntryPoint(code, extensionList));
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/must_use.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/must_use.spec.js
new file mode 100644
index 0000000000..2bff48a4ab
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/must_use.spec.js
@@ -0,0 +1,269 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for @must_use`;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 kMustUseDeclarations = {
+ var: {
+ code: `@must_use @group(0) @binding(0)
+ var<storage> x : array<u32>;`,
+ valid: false
+ },
+ function_no_return: {
+ code: `@must_use fn foo() { }`,
+ valid: false
+ },
+ function_scalar_return: {
+ code: `@must_use fn foo() -> u32 { return 0; }`,
+ valid: true
+ },
+ function_struct_return: {
+ code: `struct S { x : u32 }
+ @must_use fn foo() -> S { return S(); }`,
+ valid: true
+ },
+ function_var: {
+ code: `fn foo() { @must_use var x = 0; }`,
+ valid: false
+ },
+ function_call: {
+ code: `fn bar() -> u32 { return 0; }
+ fn foo() { @must_use bar(); }`,
+ valid: false
+ },
+ function_parameter: {
+ code: `fn foo(@must_use param : u32) -> u32 { return param; }`,
+ valid: false
+ },
+ empty_parameter: {
+ code: `@must_use() fn foo() -> u32 { return 0; }`,
+ valid: false
+ },
+ parameter: {
+ code: `@must_use(0) fn foo() -> u32 { return 0; }`,
+ valid: false
+ }
+};
+
+g.test('declaration').
+desc(`Validate attribute can only be applied to a function declaration with a return type`).
+params((u) => u.combine('test', keysOf(kMustUseDeclarations))).
+fn((t) => {
+ const test = kMustUseDeclarations[t.params.test];
+ t.expectCompileResult(test.valid, test.code);
+});
+
+const kMustUseCalls = {
+ phony: `_ = bar();`,
+ let: `let tmp = bar();`,
+ var: `var tmp = bar();`,
+ condition: `if bar() == 0 { }`,
+ param: `baz(bar());`,
+ statement: `bar();`
+};
+
+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', '']).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() {
+ ${test}
+ }`;
+ const res = t.params.call !== 'statement' || t.params.use === '';
+ t.expectCompileResult(res, code);
+});
+
+const kMustUseBuiltinCalls = {
+ // Type constructors
+ u32: `u32()`,
+ i32: `i32(0)`,
+ struct: `S()`,
+ // Reinterpretation
+ bitcast: `bitcast<f32>(8u)`,
+ // Logical
+ all: `all(vec2<bool>(true))`,
+ any: `any(vec2<bool>(true))`,
+ select: `select(0i, 1i, true)`,
+ // Array
+ arrayLength: `arrayLength(&storage_var)`,
+ // Numeric
+ abs: `abs(0.5)`,
+ acos: `acos(0.5)`,
+ acosh: `acosh(1.0)`,
+ asin: `asin(0.5)`,
+ asinh: `asinh(0.5)`,
+ atan: `atan(0.5)`,
+ atanh: `atanh(0.5)`,
+ atan2: `atan2(0.5, 0.5)`,
+ ceil: `ceil(0.5)`,
+ clamp: `clamp(0.5, 0.1, 1.0)`,
+ cos: `cos(0.5)`,
+ cosh: `cosh(0.5)`,
+ countLeadingZeros: `countLeadingZeros(0)`,
+ countOneBits: `countOneBits(0)`,
+ countTrailingZeros: `countTrailingZeros(0)`,
+ cross: `cross(vec3f(), vec3f())`,
+ degrees: `degrees(0.5)`,
+ determinant: `determinant(mat2x2f())`,
+ distance: `distance(0.5, 0.5)`,
+ dot: `dot(vec2f(0.5, 0.5), vec2f(0.5, 0.5))`,
+ exp: `exp(0.5)`,
+ exp2: `exp2(0.5)`,
+ extractBits: `extractBits(0, 0, 1)`,
+ faceForward: `faceForward(vec2f(), vec2f(), vec2f())`,
+ firstLeadingBit: `firstLeadingBit(0)`,
+ firstTrailingBit: `firstTrailingBit(0)`,
+ floor: `floor(0.5)`,
+ fma: `fma(0.5, 0.5, 0.5)`,
+ fract: `fract(0.5)`,
+ frexp: `frexp(0.5)`,
+ insertBits: `insertBits(0, 0, 0, 1)`,
+ inverseSqrt: `inverseSqrt(0.5)`,
+ ldexp: `ldexp(0.5, 1)`,
+ length: `length(0.5)`,
+ log: `log(0.5)`,
+ log2: `log2(0.5)`,
+ max: `max(0, 0)`,
+ min: `min(0, 0)`,
+ mix: `mix(0.5, 0.5, 0.5)`,
+ modf: `modf(0.5)`,
+ normalize: `normalize(vec2f(0.5, 0.5))`,
+ pow: `pow(0.5, 0.5)`,
+ quantizeToF16: `quantizeToF16(0.5)`,
+ radians: `radians(0.5)`,
+ reflect: `reflect(vec2f(0.5, 0.5), vec2f(0.5, 0.5))`,
+ refract: `refract(vec2f(0.5, 0.5), vec2f(0.5, 0.5), 0.5)`,
+ reverseBits: `reverseBits(0)`,
+ round: `round(0.5)`,
+ saturate: `saturate(0.5)`,
+ sign: `sign(0.5)`,
+ sin: `sin(0.5)`,
+ sinh: `sinh(0.5)`,
+ smoothstep: `smoothstep(0.1, 1.0, 0.5)`,
+ sqrt: `sqrt(0.5)`,
+ step: `step(0.1, 0.5)`,
+ tan: `tan(0.5)`,
+ tanh: `tanh(0.5)`,
+ transpose: `transpose(mat2x2f())`,
+ trunc: `trunc(0.5)`,
+ // Derivative
+ dpdx: `dpdx(0.5)`,
+ dpdxCoarse: `dpdxCoarse(0.5)`,
+ dpdxFine: `dpdxFine(0.5)`,
+ dpdy: `dpdy(0.5)`,
+ dpdyCoarse: `dpdyCoarse(0.5)`,
+ dpdyFine: `dpdyFine(0.5)`,
+ fwidth: `fwidth(0.5)`,
+ fwidthCoarse: `fwidthCoarse(0.5)`,
+ fwidthFine: `fwidthFine(0.5)`,
+ // Texture
+ textureDimensions: `textureDimensions(tex_2d)`,
+ textureGather: `textureGather(0, tex_2d, s, vec2f(0,0))`,
+ textureGatherCompare: `textureGatherCompare(tex_depth_2d, s_comp, vec2f(0,0), 0)`,
+ textureLoad: `textureLoad(tex_2d, vec2i(0,0), 0)`,
+ textureNumLayers: `textureNumLayers(tex_array_2d)`,
+ textureNumLevels: `textureNumLevels(tex_2d)`,
+ textureNumSamples: `textureNumSamples(tex_multi_2d)`,
+ textureSample: `textureSample(tex_2d, s, vec2f(0,0))`,
+ textureSampleBias: `textureSampleBias(tex_2d, s, vec2f(0,0), 0)`,
+ textureSampleCompare: `textureSampleCompare(tex_depth_2d, s_comp, vec2f(0,0), 0)`,
+ textureSampleCompareLevel: `textureSampleCompareLevel(tex_depth_2d, s_comp, vec2f(0,0), 0)`,
+ textureSampleGrad: `textureSampleGrad(tex_2d, s, vec2f(0,0), vec2f(0,0), vec2f(0,0))`,
+ textureSampleLevel: `textureSampleLevel(tex_2d, s, vec2f(0,0), 0)`,
+ textureSampleBaseClampToEdge: `textureSampleBaseClampToEdge(tex_2d, s, vec2f(0,0))`,
+ // Data Packing
+ pack4x8snorm: `pack4x8snorm(vec4f())`,
+ pack4x8unorm: `pack4x8unorm(vec4f())`,
+ pack2x16snorm: `pack2x16snorm(vec2f())`,
+ pack2x16unorm: `pack2x16unorm(vec2f())`,
+ pack2x16float: `pack2x16float(vec2f())`,
+ // Data Unpacking
+ unpack4x8snorm: `unpack4x8snorm(0)`,
+ unpack4x8unorm: `unpack4x8unorm(0)`,
+ unpack2x16snorm: `unpack2x16snorm(0)`,
+ unpack2x16unorm: `unpack2x16unorm(0)`,
+ unpack2x16float: `unpack2x16float(0)`,
+ // Synchronization
+ workgroupUniformLoad: `workgroupUniformLoad(&wg_var)`
+};
+
+g.test('builtin_must_use').
+desc(`Validate must_use built-in functions`).
+params((u) =>
+u.combine('call', keysOf(kMustUseBuiltinCalls)).combine('use', [true, false])
+).
+fn((t) => {
+ let call = kMustUseBuiltinCalls[t.params.call];
+ if (t.params.use) {
+ call = `_ = ${call}`;
+ }
+ const code = `
+struct S {
+ x : u32
+}
+
+@group(0) @binding(0)
+var<storage> storage_var : array<u32>;
+@group(0) @binding(1)
+var tex_2d : texture_2d<f32>;
+@group(0) @binding(2)
+var s : sampler;
+@group(0) @binding(3)
+var tex_depth_2d : texture_depth_2d;
+@group(0) @binding(4)
+var s_comp : sampler_comparison;
+@group(0) @binding(5)
+var tex_storage_2d : texture_storage_2d<rgba8unorm, write>;
+@group(0) @binding(6)
+var tex_multi_2d : texture_multisampled_2d<f32>;
+@group(0) @binding(7)
+var tex_array_2d : texture_2d_array<f32>;
+
+var<workgroup> wg_var : u32;
+
+fn foo() {
+ ${call};
+}`;
+
+ t.expectCompileResult(t.params.use, code);
+});
+
+const kNoMustUseBuiltinCalls = {
+ atomicLoad: `atomicLoad(&a)`,
+ atomicAdd: `atomicAdd(&a, 0)`,
+ atomicSub: `atomicSub(&a, 0)`,
+ atomicMax: `atomicMax(&a, 0)`,
+ atomicMin: `atomicMin(&a, 0)`,
+ atomicAnd: `atomicAnd(&a, 0)`,
+ atomicOr: `atomicOr(&a, 0)`,
+ atomicXor: `atomicXor(&a, 0)`,
+ atomicExchange: `atomicExchange(&a, 0)`,
+ atomicCompareExchangeWeak: `atomicCompareExchangeWeak(&a, 0, 0)`
+};
+
+g.test('builtin_no_must_use').
+desc(`Validate built-in functions without must_use`).
+params((u) =>
+u.combine('call', keysOf(kNoMustUseBuiltinCalls)).combine('use', [true, false])
+).
+fn((t) => {
+ let call = kNoMustUseBuiltinCalls[t.params.call];
+ if (t.params.use) {
+ call = `_ = ${call}`;
+ }
+ const code = `
+var<workgroup> a : atomic<u32>;
+
+fn foo() {
+ ${call};
+}`;
+
+ t.expectCompileResult(true, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/pipeline_stage.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/pipeline_stage.spec.js
new file mode 100644
index 0000000000..4a94012262
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/pipeline_stage.spec.js
@@ -0,0 +1,155 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for pipeline stage`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidVertex = new Set(['', '@vertex', '@\tvertex', '@/^comment^/vertex']);
+const kInvalidVertex = new Set(['@mvertex', '@vertex()', '@vertex )', '@vertex(']);
+g.test('vertex_parsing').
+desc(`Test that @vertex is parsed correctly.`).
+params((u) => u.combine('val', new Set([...kValidVertex, ...kInvalidVertex]))).
+fn((t) => {
+ const v = t.params.val.replace(/\^/g, '*');
+ const r = t.params.val !== '' ? '@builtin(position)' : '';
+ const code = `
+${v}
+fn main() -> ${r} vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kValidVertex.has(t.params.val), code);
+});
+
+const kValidFragment = new Set(['', '@fragment', '@\tfragment', '@/^comment^/fragment']);
+const kInvalidFragment = new Set(['@mfragment', '@fragment()', '@fragment )', '@fragment(']);
+g.test('fragment_parsing').
+desc(`Test that @fragment is parsed correctly.`).
+params((u) => u.combine('val', new Set([...kValidFragment, ...kInvalidFragment]))).
+fn((t) => {
+ const v = t.params.val.replace(/\^/g, '*');
+ const r = t.params.val !== '' ? '@location(0)' : '';
+ const code = `
+${v}
+fn main() -> ${r} vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kValidFragment.has(t.params.val), code);
+});
+
+const kValidCompute = new Set(['', '@compute', '@\tcompute', '@/^comment^/compute']);
+const kInvalidCompute = new Set(['@mcompute', '@compute()', '@compute )', '@compute(']);
+g.test('compute_parsing').
+desc(`Test that @compute is parsed correctly.`).
+params((u) => u.combine('val', new Set([...kValidCompute, ...kInvalidCompute]))).
+fn((t) => {
+ let v = t.params.val.replace(/\^/g, '*');
+ // Always add a workgroup size unless there is no parameter
+ if (v !== '') {
+ v += '\n@workgroup_size(1)';
+ }
+ const code = `
+${v}
+fn main() {}
+`;
+ t.expectCompileResult(kValidCompute.has(t.params.val), code);
+});
+
+g.test('multiple_entry_points').
+desc(`Test that multiple entry points are allowed.`).
+fn((t) => {
+ const code = `
+@compute @workgroup_size(1) fn compute_1() {}
+@compute @workgroup_size(1) fn compute_2() {}
+
+@fragment fn frag_1() -> @location(2) vec4f { return vec4f(1); }
+@fragment fn frag_2() -> @location(2) vec4f { return vec4f(1); }
+@fragment fn frag_3() -> @location(2) vec4f { return vec4f(1); }
+
+@vertex fn vtx_1() -> @builtin(position) vec4f { return vec4f(1); }
+@vertex fn vtx_2() -> @builtin(position) vec4f { return vec4f(1); }
+@vertex fn vtx_3() -> @builtin(position) vec4f { return vec4f(1); }
+`;
+ 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'])).
+fn((t) => {
+ const code = `
+@compute ${t.params.dupe} @workgroup_size(1) fn compute_1() {}
+`;
+ t.expectCompileResult(t.params.dupe === '', code);
+});
+
+g.test('duplicate_fragment_on_function').
+desc(`Test that duplcate @fragment attributes are not allowed.`).
+params((u) => u.combine('dupe', ['', '@fragment'])).
+fn((t) => {
+ const code = `
+@fragment ${t.params.dupe} fn vtx() -> @location(0) vec4f { return vec4f(1); }
+`;
+ t.expectCompileResult(t.params.dupe === '', code);
+});
+
+g.test('duplicate_vertex_on_function').
+desc(`Test that duplcate @vertex attributes are not allowed.`).
+params((u) => u.combine('dupe', ['', '@vertex'])).
+fn((t) => {
+ const code = `
+@vertex ${t.params.dupe} fn vtx() -> @builtin(position) vec4f { return vec4f(1); }
+`;
+ t.expectCompileResult(t.params.dupe === '', code);
+});
+
+g.test('placement').
+desc('Tests the locations @align is allowed to appear').
+params((u) =>
+u.
+combine('scope', [
+'private-var',
+'storage-var',
+'struct-member',
+'fn-param',
+'fn-var',
+'fn-return',
+'while-stmt',
+undefined]
+).
+combine('attr', ['@compute', '@fragment', '@vertex'])
+).
+fn((t) => {
+ const scope = t.params.scope;
+
+ const attr = t.params.attr;
+ const code = `
+ ${scope === 'private-var' ? attr : ''}
+ var<private> priv_var : i32;
+
+ ${scope === 'storage-var' ? attr : ''}
+ @group(0) @binding(0)
+ var<storage> stor_var : i32;
+
+ struct A {
+ ${scope === 'struct-member' ? attr : ''}
+ a : i32,
+ }
+
+ @vertex
+ fn f(
+ ${scope === 'fn-param' ? attr : ''}
+ @location(0) b : i32,
+ ) -> ${scope === 'fn-return' ? attr : ''} @builtin(position) vec4f {
+ ${scope === 'fn-var' ? attr : ''}
+ var<function> func_v : i32;
+
+ ${scope === 'while-stmt' ? attr : ''}
+ while false {}
+
+ return vec4(1, 1, 1, 1);
+ }
+ `;
+
+ t.expectCompileResult(scope === undefined, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/semicolon.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/semicolon.spec.js
new file mode 100644
index 0000000000..f7af6a0b3e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/semicolon.spec.js
@@ -0,0 +1,269 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for semicolon placements`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('module_scope_single').
+desc(`Test that a semicolon can be placed at module scope.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `;`);
+});
+
+g.test('module_scope_multiple').
+desc(`Test that multiple semicolons can be placed at module scope.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `;;;`);
+});
+
+g.test('after_enable').
+desc(`Test that a semicolon must be placed after an enable directive.`).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+}).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `enable f16;`);
+ t.expectCompileResult( /* pass */false, `enable f16`);
+});
+
+g.test('after_struct_decl').
+desc(`Test that a semicolon can be placed after an struct declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `struct S { x : i32 };`);
+ t.expectCompileResult( /* pass */true, `struct S { x : i32 }`);
+});
+
+g.test('after_member').
+desc(`Test that a semicolon must not be placed after an struct member declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `struct S { x : i32 }`);
+ t.expectCompileResult( /* pass */false, `struct S { x : i32; }`);
+});
+
+g.test('after_func_decl').
+desc(`Test that a semicolon can be placed after a function declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() {};`);
+ t.expectCompileResult( /* pass */true, `fn f() {}`);
+});
+
+g.test('after_type_alias_decl').
+desc(`Test that a semicolon must be placed after an type alias declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `alias T = i32;`);
+ t.expectCompileResult( /* pass */false, `alias T = i32`);
+});
+
+g.test('after_return').
+desc(`Test that a semicolon must be placed after a return statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { return; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { return }`);
+});
+
+g.test('after_call').
+desc(`Test that a semicolon must be placed after a function call.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { workgroupBarrier(); }`);
+ t.expectCompileResult( /* pass */false, `fn f() { workgroupBarrier() }`);
+});
+
+g.test('after_module_const_decl').
+desc(`Test that a semicolon must be placed after a module-scope const declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `const v = 1;`);
+ t.expectCompileResult( /* pass */false, `const v = 1`);
+});
+
+g.test('after_fn_const_decl').
+desc(`Test that a semicolon must be placed after a function-scope const declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { const v = 1; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { const v = 1 }`);
+});
+
+g.test('after_module_var_decl').
+desc(`Test that a semicolon must be placed after a module-scope var declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `var<private> v = 1;`);
+ t.expectCompileResult( /* pass */false, `var<private> v = 1`);
+});
+
+g.test('after_fn_var_decl').
+desc(`Test that a semicolon must be placed after a function-scope var declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { var v = 1; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { var v = 1 }`);
+});
+
+g.test('after_let_decl').
+desc(`Test that a semicolon must be placed after a let declaration.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { let v = 1; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { let v = 1 }`);
+});
+
+g.test('after_discard').
+desc(`Test that a semicolon must be placed after a discard statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { discard; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { discard }`);
+});
+
+g.test('after_assignment').
+desc(`Test that a semicolon must be placed after an assignment statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { var v = 1; v = 2; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { var v = 1; v = 2 }`);
+});
+
+g.test('after_fn_const_assert').
+desc(`Test that a semicolon must be placed after an function-scope static assert.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { const_assert(true); }`);
+ t.expectCompileResult( /* pass */false, `fn f() { const_assert(true) }`);
+});
+
+g.test('function_body_single').
+desc(`Test that a semicolon can be placed in a function body.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { ; }`);
+});
+
+g.test('function_body_multiple').
+desc(`Test that multiple semicolons can be placed in a function body.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { ;;; }`);
+});
+
+g.test('compound_statement_single').
+desc(`Test that a semicolon can be placed in a compound statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { { ; } }`);
+});
+
+g.test('compound_statement_multiple').
+desc(`Test that multiple semicolons can be placed in a compound statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { { ;;; } }`);
+});
+
+g.test('after_compound_statement').
+desc(`Test that a semicolon can be placed after a compound statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { {} ; }`);
+});
+
+g.test('after_if').
+desc(`Test that a semicolon can be placed after an if-statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { if true {} ; }`);
+});
+
+g.test('after_if_else').
+desc(`Test that a semicolon can be placed after an if-else-statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { if true {} else {} ; }`);
+});
+
+g.test('after_switch').
+desc(`Test that a semicolon can be placed after an switch-statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { switch 1 { default {} } ; }`);
+});
+
+g.test('after_case').
+desc(`Test that a semicolon cannot be placed after a non-default switch case.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */false, `fn f() { switch 1 { case 1 {}; default {} } }`);
+ t.expectCompileResult( /* pass */true, `fn f() { switch 1 { case 1 {} default {} } }`);
+});
+
+g.test('after_case_break').
+desc(`Test that a semicolon must be placed after a case break statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */false, `fn f() { switch 1 { case 1 { break } default {} } }`);
+ t.expectCompileResult( /* pass */true, `fn f() { switch 1 { case 1 { break; } default {} } }`);
+});
+
+g.test('after_default_case').
+desc(`Test that a semicolon cannot be placed after a default switch case.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */false, `fn f() { switch 1 { default {}; } }`);
+ t.expectCompileResult( /* pass */true, `fn f() { switch 1 { default {} } }`);
+});
+
+g.test('after_default_case_break').
+desc(`Test that a semicolon cannot be placed after a default switch case.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */false, `fn f() { switch 1 { default { break } } }`);
+ t.expectCompileResult( /* pass */true, `fn f() { switch 1 { default { break; } } }`);
+});
+
+g.test('after_for').
+desc(`Test that a semicolon can be placed after a for-loop.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { for (; false;) {}; }`);
+});
+
+g.test('after_for_break').
+desc(`Test that a semicolon must be placed after a for-loop break statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { for (; false;) { break; } }`);
+ t.expectCompileResult( /* pass */false, `fn f() { for (; false;) { break } }`);
+});
+
+g.test('after_loop').
+desc(`Test that a semicolon can be placed after a loop.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { loop { break; }; }`);
+});
+
+g.test('after_loop_break').
+desc(`Test that a semicolon must be placed after a loop break statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { loop { break; }; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { loop { break }; }`);
+});
+
+g.test('after_loop_break_if').
+desc(`Test that a semicolon must be placed after a loop break-if statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { loop { continuing { break if true; } }; }`);
+ t.expectCompileResult( /* pass */false, `fn f() { loop { continuing { break if true } }; }`);
+});
+
+g.test('after_loop_continue').
+desc(`Test that a semicolon must be placed after a loop continue statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { loop { if true { continue; } { break; } } }`);
+ t.expectCompileResult( /* pass */false, `fn f() { loop { if true { continue } { break; } } }`);
+});
+
+g.test('after_continuing').
+desc(`Test that a semicolon cannot be placed after a continuing.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */false, `fn f() { loop { break; continuing{}; } }`);
+ t.expectCompileResult( /* pass */true, `fn f() { loop { break; continuing{} } }`);
+});
+
+g.test('after_while').
+desc(`Test that a semicolon cannot be placed after a while-loop.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { while false {}; }`);
+});
+
+g.test('after_while_break').
+desc(`Test that a semicolon must be placed after a while break statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { while false { break; } }`);
+ t.expectCompileResult( /* pass */false, `fn f() { while false { break } }`);
+});
+
+g.test('after_while_continue').
+desc(`Test that a semicolon must be placed after a while continue statement.`).
+fn((t) => {
+ t.expectCompileResult( /* pass */true, `fn f() { while false { continue; } }`);
+ t.expectCompileResult( /* pass */false, `fn f() { while false { continue } }`);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/source.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/source.spec.js
new file mode 100644
index 0000000000..11f28d904a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/source.spec.js
@@ -0,0 +1,29 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for source parsing`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('valid_source').
+desc(`Tests that a valid source is consumed successfully.`).
+fn((t) => {
+ const code = `
+ @fragment
+ fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(true, code);
+});
+
+g.test('empty').
+desc(`Test that an empty source is consumed successfully.`).
+fn((t) => {
+ t.expectCompileResult(true, '');
+});
+
+g.test('invalid_source').
+desc(`Tests that a source which does not match the grammar fails.`).
+fn((t) => {
+ t.expectCompileResult(false, 'invalid_source');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/unary_ops.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/unary_ops.spec.js
new file mode 100644
index 0000000000..952cb9a8ed
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/unary_ops.spec.js
@@ -0,0 +1,48 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for unary ops`;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 = {
+ not_bool_literal: {
+ src: 'let a = !true;',
+ pass: true
+ },
+ not_bool_expr: {
+ src: `let a = !(1 == 2);`,
+ pass: true
+ },
+ not_not_bool_literal: {
+ src: 'let a = !!true;',
+ pass: true
+ },
+ not_not_bool_expr: {
+ src: `let a = !!(1 == 2);`,
+ pass: true
+ },
+ not_int_literal: {
+ src: `let a = !42;`,
+ pass: false
+ },
+ not_int_expr: {
+ src: `let a = !(40 + 2);`,
+ pass: false
+ }
+};
+
+g.test('all').
+desc('Test that unary operators are 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/var_and_let.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/var_and_let.spec.js
new file mode 100644
index 0000000000..75bf64e7eb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/parse/var_and_let.spec.js
@@ -0,0 +1,106 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Positive and negative validation tests for variable and const.
+
+TODO: Find a better way to test arrays than using a single arbitrary size. [1]
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTestTypes = [
+'f32',
+'i32',
+'u32',
+'bool',
+'vec2<f32>',
+'vec2<i32>',
+'vec2<u32>',
+'vec2<bool>',
+'vec3<f32>',
+'vec3<i32>',
+'vec3<u32>',
+'vec3<bool>',
+'vec4<f32>',
+'vec4<i32>',
+'vec4<u32>',
+'vec4<bool>',
+'mat2x2<f32>',
+'mat2x3<f32>',
+'mat2x4<f32>',
+'mat3x2<f32>',
+'mat3x3<f32>',
+'mat3x4<f32>',
+'mat4x2<f32>',
+'mat4x3<f32>',
+'mat4x4<f32>',
+// [1]: 12 is a random number here. find a solution to replace it.
+'array<f32, 12>',
+'array<i32, 12>',
+'array<u32, 12>',
+'array<bool, 12>'];
+
+
+g.test('initializer_type').
+desc(
+ `
+ If present, the initializer's type must match the store type of the variable.
+ Testing scalars, vectors, and matrices of every dimension and type.
+ TODO: add test for: structs - arrays of vectors and matrices - arrays of different length
+`
+).
+params((u) =>
+u.
+combine('variableOrConstant', ['var', 'let']).
+beginSubcases().
+combine('lhsType', kTestTypes).
+combine('rhsType', kTestTypes)
+).
+fn((t) => {
+ const { variableOrConstant, lhsType, rhsType } = t.params;
+
+ const code = `
+ @fragment
+ fn main() {
+ ${variableOrConstant} a : ${lhsType} = ${rhsType}();
+ }
+ `;
+
+ const expectation = lhsType === rhsType;
+ t.expectCompileResult(expectation, code);
+});
+
+g.test('var_access_mode_bad_other_template_contents').
+desc(
+ 'A variable declaration with explicit access mode with varying other template list contents'
+).
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#var-decls').
+params((u) =>
+u.
+combine('accessMode', ['read', 'read_write']).
+combine('prefix', ['storage,', '', ',']).
+combine('suffix', [',storage', ',read', ',', ''])
+).
+fn((t) => {
+ const prog = `@group(0) @binding(0)
+ var<${t.params.prefix}${t.params.accessMode}${t.params.suffix}> x: i32;`;
+ const ok = t.params.prefix === 'storage,' && t.params.suffix === '';
+ t.expectCompileResult(ok, prog);
+});
+
+g.test('var_access_mode_bad_template_delim').
+desc('A variable declaration has explicit access mode with varying template list delimiters').
+specURL('https://gpuweb.github.io/gpuweb/wgsl/#var-decls').
+params((u) =>
+u.
+combine('accessMode', ['read', 'read_write']).
+combine('prefix', ['', '<', '>', ',']).
+combine('suffix', ['', '<', '>', ','])
+).
+fn((t) => {
+ const prog = `@group(0) @binding(0)
+ var ${t.params.prefix}storage,${t.params.accessMode}${t.params.suffix} x: i32;`;
+ const ok = t.params.prefix === '<' && t.params.suffix === '>';
+ t.expectCompileResult(ok, prog);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/binding.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/binding.spec.js
new file mode 100644
index 0000000000..d87af51d30
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/binding.spec.js
@@ -0,0 +1,140 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for binding`;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 = {
+ const_expr: {
+ src: `const z = 5;
+ const y = 2;
+ @binding(z + y)`,
+ pass: true
+ },
+ override_expr: {
+ src: `override z = 5;
+ @binding(z)`,
+ pass: false
+ },
+
+ zero: {
+ src: `@binding(0)`,
+ pass: true
+ },
+ one: {
+ src: `@binding(1)`,
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */binding(1)`,
+ pass: true
+ },
+ split_line: {
+ src: '@ \n binding(1)',
+ pass: true
+ },
+ trailing_comma: {
+ src: `@binding(1,)`,
+ pass: true
+ },
+ int_literal: {
+ src: `@binding(1i)`,
+ pass: true
+ },
+ uint_literal: {
+ src: `@binding(1u)`,
+ pass: true
+ },
+ hex_literal: {
+ src: `@binding(0x1)`,
+ pass: true
+ },
+
+ negative: {
+ src: `@binding(-1)`,
+ pass: false
+ },
+ missing_value: {
+ src: `@binding()`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@binding 1)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@binding(1`,
+ pass: false
+ },
+ multiple_values: {
+ src: `@binding(1,2)`,
+ pass: false
+ },
+ f32_val_literal: {
+ src: `@binding(1.0)`,
+ pass: false
+ },
+ f32_val: {
+ src: `@binding(1f)`,
+ pass: false
+ },
+ no_params: {
+ src: `@binding`,
+ pass: false
+ },
+ misspelling: {
+ src: `@abinding(1)`,
+ pass: false
+ },
+ multi_binding: {
+ src: `@binding(1) @binding(1)`,
+ pass: false
+ }
+};
+g.test('binding').
+desc(`Test validation of binding`).
+params((u) => u.combine('attr', keysOf(kTests))).
+fn((t) => {
+ const code = `
+${kTests[t.params.attr].src} @group(1)
+var<storage> a: i32;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ _ = a;
+}`;
+ t.expectCompileResult(kTests[t.params.attr].pass, code);
+});
+
+g.test('binding_f16').
+desc(`Test validation of binding with f16`).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const code = `
+@group(1) @binding(1h)
+var<storage> a: i32;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ _ = a;
+}`;
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/builtins.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/builtins.spec.js
new file mode 100644
index 0000000000..07ea75828e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/builtins.spec.js
@@ -0,0 +1,277 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for entry point built-in variables`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import { generateShader } from './util.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// List of all built-in variables and their stage, in|out usage, and type.
+// Taken from table in Section 15:
+// https://www.w3.org/TR/2021/WD-WGSL-20211013/#builtin-variables
+export const kBuiltins = [
+{ name: 'vertex_index', stage: 'vertex', io: 'in', type: 'u32' },
+{ name: 'instance_index', stage: 'vertex', io: 'in', type: 'u32' },
+{ name: 'position', stage: 'vertex', io: 'out', type: 'vec4<f32>' },
+{ name: 'position', stage: 'fragment', io: 'in', type: 'vec4<f32>' },
+{ name: 'front_facing', stage: 'fragment', io: 'in', type: 'bool' },
+{ name: 'frag_depth', stage: 'fragment', io: 'out', type: 'f32' },
+{ name: 'local_invocation_id', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+{ name: 'local_invocation_index', stage: 'compute', io: 'in', type: 'u32' },
+{ name: 'global_invocation_id', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+{ name: 'workgroup_id', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+{ name: 'num_workgroups', stage: 'compute', io: 'in', type: 'vec3<u32>' },
+{ name: 'sample_index', stage: 'fragment', io: 'in', type: 'u32' },
+{ name: 'sample_mask', stage: 'fragment', io: 'in', type: 'u32' },
+{ name: 'sample_mask', stage: 'fragment', io: 'out', type: 'u32' }];
+
+
+// List of types to test against.
+const kTestTypes = [
+'bool',
+'u32',
+'i32',
+'f32',
+'vec2<bool>',
+'vec2<u32>',
+'vec2<i32>',
+'vec2<f32>',
+'vec3<bool>',
+'vec3<u32>',
+'vec3<i32>',
+'vec3<f32>',
+'vec4<bool>',
+'vec4<u32>',
+'vec4<i32>',
+'vec4<f32>',
+'mat2x2<f32>',
+'mat2x3<f32>',
+'mat2x4<f32>',
+'mat3x2<f32>',
+'mat3x3<f32>',
+'mat3x4<f32>',
+'mat4x2<f32>',
+'mat4x3<f32>',
+'mat4x4<f32>',
+'atomic<u32>',
+'atomic<i32>',
+'array<bool,4>',
+'array<u32,4>',
+'array<i32,4>',
+'array<f32,4>',
+'MyStruct'];
+
+
+g.test('stage_inout').
+desc(
+ `Test that each @builtin attribute is validated against the required stage and in/out usage for that built-in variable.`
+).
+params((u) =>
+u.
+combineWithParams(kBuiltins).
+combine('use_struct', [true, false]).
+combine('target_stage', ['', 'vertex', 'fragment', 'compute']).
+combine('target_io', ['in', 'out']).
+beginSubcases()
+).
+fn((t) => {
+ const code = generateShader({
+ attribute: `@builtin(${t.params.name})`,
+ type: t.params.type,
+ stage: t.params.target_stage,
+ io: t.params.target_io,
+ use_struct: t.params.use_struct
+ });
+
+ // Expect to pass iff the built-in table contains an entry that matches.
+ const expectation = kBuiltins.some(
+ (x) =>
+ x.name === t.params.name && (
+ x.stage === t.params.target_stage ||
+ t.params.use_struct && t.params.target_stage === '') && (
+ x.io === t.params.target_io || t.params.target_stage === '') &&
+ x.type === t.params.type
+ );
+ t.expectCompileResult(expectation, code);
+});
+
+g.test('type').
+desc(
+ `Test that each @builtin attribute is validated against the required type of that built-in variable.`
+).
+params((u) =>
+u.
+combineWithParams(kBuiltins).
+combine('use_struct', [true, false]).
+combine('target_type', kTestTypes).
+beginSubcases()
+).
+fn((t) => {
+ let code = '';
+
+ if (t.params.target_type === 'MyStruct') {
+ // Generate a struct that contains the correct built-in type.
+ code += 'struct MyStruct {\n';
+ code += ` value : ${t.params.type}\n`;
+ code += '};\n\n';
+ }
+
+ code += generateShader({
+ attribute: `@builtin(${t.params.name})`,
+ type: t.params.target_type,
+ stage: t.params.stage,
+ io: t.params.io,
+ use_struct: t.params.use_struct
+ });
+
+ // Expect to pass iff the built-in table contains an entry that matches.
+ const expectation = kBuiltins.some(
+ (x) =>
+ x.name === t.params.name &&
+ x.stage === t.params.stage &&
+ x.io === t.params.io &&
+ x.type === t.params.target_type
+ );
+ t.expectCompileResult(expectation, code);
+});
+
+g.test('nesting').
+desc(`Test validation of nested built-in variables`).
+params((u) =>
+u.
+combine('target_stage', ['fragment', '']).
+combine('target_io', ['in', 'out']).
+beginSubcases()
+).
+fn((t) => {
+ // Generate a struct that contains a sample_mask builtin, nested inside another struct.
+ let code = `
+ struct Inner {
+ @builtin(sample_mask) value : u32
+ };
+ struct Outer {
+ inner : Inner
+ };`;
+
+ code += generateShader({
+ attribute: '',
+ type: 'Outer',
+ stage: t.params.target_stage,
+ io: t.params.target_io,
+ use_struct: false
+ });
+
+ // Expect to pass only if the struct is not used for entry point IO.
+ t.expectCompileResult(t.params.target_stage === '', code);
+});
+
+g.test('duplicates').
+desc(`Test that duplicated built-in variables are validated.`).
+params((u) =>
+u
+// Place two @builtin(sample_mask) attributes onto the entry point function.
+// We use `sample_mask` as it is valid as both an input and output for the same entry point.
+// The function:
+// - has two non-struct parameters (`p1` and `p2`)
+// - has two struct parameters each with two members (`s1{a,b}` and `s2{a,b}`)
+// - returns a struct with two members (`ra` and `rb`)
+// By default, all of these variables will have unique @location() attributes.
+.combine('first', ['p1', 's1a', 's2a', 'ra']).
+combine('second', ['p2', 's1b', 's2b', 'rb']).
+beginSubcases()
+).
+fn((t) => {
+ const p1 =
+ t.params.first === 'p1' ? '@builtin(sample_mask)' : '@location(1) @interpolate(flat)';
+ const p2 =
+ t.params.second === 'p2' ? '@builtin(sample_mask)' : '@location(2) @interpolate(flat)';
+ const s1a =
+ t.params.first === 's1a' ? '@builtin(sample_mask)' : '@location(3) @interpolate(flat)';
+ const s1b =
+ t.params.second === 's1b' ? '@builtin(sample_mask)' : '@location(4) @interpolate(flat)';
+ const s2a =
+ t.params.first === 's2a' ? '@builtin(sample_mask)' : '@location(5) @interpolate(flat)';
+ const s2b =
+ t.params.second === 's2b' ? '@builtin(sample_mask)' : '@location(6) @interpolate(flat)';
+ const ra =
+ t.params.first === 'ra' ? '@builtin(sample_mask)' : '@location(1) @interpolate(flat)';
+ const rb =
+ t.params.second === 'rb' ? '@builtin(sample_mask)' : '@location(2) @interpolate(flat)';
+ const code = `
+ struct S1 {
+ ${s1a} a : u32,
+ ${s1b} b : u32,
+ };
+ struct S2 {
+ ${s2a} a : u32,
+ ${s2b} b : u32,
+ };
+ struct R {
+ ${ra} a : u32,
+ ${rb} b : u32,
+ };
+ @fragment
+ fn main(${p1} p1 : u32,
+ ${p2} p2 : u32,
+ s1 : S1,
+ s2 : S2,
+ ) -> R {
+ return R();
+ }
+ `;
+
+ // The test should fail if both @builtin(sample_mask) attributes are on the input parameters
+ // or structures, or it they are both on the output struct. Otherwise it should pass.
+ const firstIsRet = t.params.first === 'ra';
+ const secondIsRet = t.params.second === 'rb';
+ const expectation = firstIsRet !== secondIsRet;
+ t.expectCompileResult(expectation, code);
+});
+
+g.test('missing_vertex_position').
+desc(`Test that vertex shaders are required to output @builtin(position).`).
+params((u) =>
+u.
+combine('use_struct', [true, false]).
+combine('attribute', ['@builtin(position)', '@location(0)']).
+beginSubcases()
+).
+fn((t) => {
+ const code = `
+ struct S {
+ ${t.params.attribute} value : vec4<f32>
+ };
+
+ @vertex
+ fn main() -> ${t.params.use_struct ? 'S' : `${t.params.attribute} vec4<f32>`} {
+ return ${t.params.use_struct ? 'S' : 'vec4<f32>'}();
+ }
+ `;
+
+ // Expect to pass only when using @builtin(position).
+ t.expectCompileResult(t.params.attribute === '@builtin(position)', code);
+});
+
+g.test('reuse_builtin_name').
+desc(`Test that a builtin name can be used in different contexts`).
+params((u) =>
+u.
+combineWithParams(kBuiltins).
+combine('use', ['alias', 'struct', 'function', 'module-var', 'function-var'])
+).
+fn((t) => {
+ let code = '';
+ if (t.params.use === 'alias') {
+ code += `alias ${t.params.name} = i32;`;
+ } else if (t.params.use === `struct`) {
+ code += `struct ${t.params.name} { i: f32, }`;
+ } else if (t.params.use === `function`) {
+ code += `fn ${t.params.name}() {}`;
+ } else if (t.params.use === `module-var`) {
+ code += `const ${t.params.name} = 1;`;
+ } else if (t.params.use === `function-var`) {
+ code += `fn test() { let ${t.params.name} = 1; }`;
+ }
+ t.expectCompileResult(true, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/entry_point.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/entry_point.spec.js
new file mode 100644
index 0000000000..d56b69811a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/entry_point.spec.js
@@ -0,0 +1,141 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for attributes and entry point requirements`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('missing_attribute_on_param').
+desc(`Test that an entry point without an IO attribute on one of its parameters is rejected.`).
+params((u) =>
+u.combine('target_stage', ['', 'vertex', 'fragment', 'compute']).beginSubcases()
+).
+fn((t) => {
+ const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@location(1)';
+ const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(1)';
+ const compute_attr = t.params.target_stage === 'compute' ? '' : '@builtin(workgroup_id)';
+ const code = `
+@vertex
+fn vert_main(@location(0) a : f32,
+ ${vertex_attr} b : f32,
+@ location(2) c : f32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+}
+
+@fragment
+fn frag_main(@location(0) a : f32,
+ ${fragment_attr} b : f32,
+@ location(2) c : f32) {
+}
+
+@compute @workgroup_size(1)
+fn comp_main(@builtin(global_invocation_id) a : vec3<u32>,
+ ${compute_attr} b : vec3<u32>,
+ @builtin(local_invocation_id) c : vec3<u32>) {
+}
+`;
+ t.expectCompileResult(t.params.target_stage === '', code);
+});
+
+g.test('missing_attribute_on_param_struct').
+desc(
+ `Test that an entry point struct parameter without an IO attribute on one of its members is rejected.`
+).
+params((u) =>
+u.combine('target_stage', ['', 'vertex', 'fragment', 'compute']).beginSubcases()
+).
+fn((t) => {
+ const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@location(1)';
+ const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(1)';
+ const compute_attr = t.params.target_stage === 'compute' ? '' : '@builtin(workgroup_id)';
+ const code = `
+struct VertexInputs {
+ @location(0) a : f32,
+ ${vertex_attr} b : f32,
+@ location(2) c : f32,
+};
+struct FragmentInputs {
+ @location(0) a : f32,
+ ${fragment_attr} b : f32,
+@ location(2) c : f32,
+};
+struct ComputeInputs {
+ @builtin(global_invocation_id) a : vec3<u32>,
+ ${compute_attr} b : vec3<u32>,
+ @builtin(local_invocation_id) c : vec3<u32>,
+};
+
+@vertex
+fn vert_main(inputs : VertexInputs) -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+}
+
+@fragment
+fn frag_main(inputs : FragmentInputs) {
+}
+
+@compute @workgroup_size(1)
+fn comp_main(inputs : ComputeInputs) {
+}
+`;
+ t.expectCompileResult(t.params.target_stage === '', code);
+});
+
+g.test('missing_attribute_on_return_type').
+desc(`Test that an entry point without an IO attribute on its return type is rejected.`).
+params((u) => u.combine('target_stage', ['', 'vertex', 'fragment']).beginSubcases()).
+fn((t) => {
+ const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@builtin(position)';
+ const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(0)';
+ const code = `
+@vertex
+fn vert_main() -> ${vertex_attr} vec4<f32> {
+ return vec4<f32>();
+}
+
+@fragment
+fn frag_main() -> ${fragment_attr} vec4<f32> {
+ return vec4<f32>();
+}
+`;
+ t.expectCompileResult(t.params.target_stage === '', code);
+});
+
+g.test('missing_attribute_on_return_type_struct').
+desc(
+ `Test that an entry point struct return type without an IO attribute on one of its members is rejected.`
+).
+params((u) => u.combine('target_stage', ['', 'vertex', 'fragment']).beginSubcases()).
+fn((t) => {
+ const vertex_attr = t.params.target_stage === 'vertex' ? '' : '@location(1)';
+ const fragment_attr = t.params.target_stage === 'fragment' ? '' : '@location(1)';
+ const code = `
+struct VertexOutputs {
+ @location(0) a : f32,
+ ${vertex_attr} b : f32,
+ @builtin(position) c : vec4<f32>,
+};
+struct FragmentOutputs {
+ @location(0) a : f32,
+ ${fragment_attr} b : f32,
+@ location(2) c : f32,
+};
+
+@vertex
+fn vert_main() -> VertexOutputs {
+ return VertexOutputs();
+}
+
+@fragment
+fn frag_main() -> FragmentOutputs {
+ return FragmentOutputs();
+}
+`;
+ t.expectCompileResult(t.params.target_stage === '', code);
+});
+
+g.test('no_entry_point_provided').
+desc(`Tests that a shader without an entry point is accepted`).
+fn((t) => {
+ t.expectCompileResult(true, 'fn main() {}');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group.spec.js
new file mode 100644
index 0000000000..4c6a691e52
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group.spec.js
@@ -0,0 +1,140 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for group`;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 = {
+ const_expr: {
+ src: `const z = 5;
+ const y = 2;
+ @group(z + y)`,
+ pass: true
+ },
+ override_expr: {
+ src: `override z = 5;
+ @group(z)`,
+ pass: false
+ },
+
+ zero: {
+ src: `@group(0)`,
+ pass: true
+ },
+ one: {
+ src: `@group(1)`,
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */group(1)`,
+ pass: true
+ },
+ split_line: {
+ src: '@ \n group(1)',
+ pass: true
+ },
+ trailing_comma: {
+ src: `@group(1,)`,
+ pass: true
+ },
+ int_literal: {
+ src: `@group(1i)`,
+ pass: true
+ },
+ uint_literal: {
+ src: `@group(1u)`,
+ pass: true
+ },
+ hex_literal: {
+ src: `@group(0x1)`,
+ pass: true
+ },
+
+ negative: {
+ src: `@group(-1)`,
+ pass: false
+ },
+ missing_value: {
+ src: `@group()`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@group 1)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@group(1`,
+ pass: false
+ },
+ multiple_values: {
+ src: `@group(1,2)`,
+ pass: false
+ },
+ f32_val_literal: {
+ src: `@group(1.0)`,
+ pass: false
+ },
+ f32_val: {
+ src: `@group(1f)`,
+ pass: false
+ },
+ no_params: {
+ src: `@group`,
+ pass: false
+ },
+ misspelling: {
+ src: `@agroup(1)`,
+ pass: false
+ },
+ multi_group: {
+ src: `@group(1) @group(1)`,
+ pass: false
+ }
+};
+g.test('group').
+desc(`Test validation of group`).
+params((u) => u.combine('attr', keysOf(kTests))).
+fn((t) => {
+ const code = `
+${kTests[t.params.attr].src} @binding(1)
+var<storage> a: i32;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ _ = a;
+}`;
+ t.expectCompileResult(kTests[t.params.attr].pass, code);
+});
+
+g.test('group_f16').
+desc(`Test validation of group with f16`).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const code = `
+@group(1h) @binding(1)
+var<storage> a: i32;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ _ = a;
+}`;
+ 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);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group_and_binding.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group_and_binding.spec.js
new file mode 100644
index 0000000000..d554700474
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/group_and_binding.spec.js
@@ -0,0 +1,171 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for group and binding`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import {
+ declareEntrypoint,
+ kResourceEmitters,
+ kResourceKindsA,
+ kResourceKindsAll,
+ kResourceKindsB } from
+
+'./util.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('binding_attributes').
+desc(`Test that both @group and @binding attributes must both be declared.`).
+params((u) =>
+u.
+combine('stage', ['vertex', 'fragment', 'compute']).
+combine('has_group', [true, false]).
+combine('has_binding', [true, false]).
+combine('resource', kResourceKindsAll).
+beginSubcases()
+).
+fn((t) => {
+ const emitter = kResourceEmitters.get(t.params.resource);
+ //const emitter = kResourceEmitters.get('uniform') as ResourceDeclarationEmitter;
+ const code = emitter(
+ 'R',
+ t.params.has_group ? 0 : undefined,
+ t.params.has_binding ? 0 : undefined
+ );
+ const expect = t.params.has_group && t.params.has_binding;
+ t.expectCompileResult(expect, code);
+});
+
+g.test('private_module_scope').
+desc(`Test validation of group and binding on private resources`).
+fn((t) => {
+ const code = `
+@group(1) @binding(1)
+var<private> a: i32;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ _ = a;
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('private_function_scope').
+desc(`Test validation of group and binding on function-scope private resources`).
+fn((t) => {
+ const code = `
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ @group(1) @binding(1)
+ var<private> a: i32;
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('function_scope').
+desc(`Test validation of group and binding on function-scope private resources`).
+fn((t) => {
+ const code = `
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ @group(1) @binding(1)
+ var a: i32;
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('function_scope_texture').
+desc(`Test validation of group and binding on function-scope private resources`).
+fn((t) => {
+ const code = `
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ @group(1) @binding(1)
+ var a: texture_2d<f32>;
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('single_entry_point').
+desc(
+ `Test that two different resource variables in a shader must not have the same group and binding values, when considered as a pair.`
+).
+params((u) =>
+u.
+combine('stage', ['vertex', 'fragment', 'compute']).
+combine('a_kind', kResourceKindsA).
+combine('b_kind', kResourceKindsB).
+combine('a_group', [0, 3]).
+combine('b_group', [0, 3]).
+combine('a_binding', [0, 3]).
+combine('b_binding', [0, 3]).
+combine('usage', ['direct', 'transitive']).
+beginSubcases()
+).
+fn((t) => {
+ const resourceA = kResourceEmitters.get(t.params.a_kind);
+ const resourceB = kResourceEmitters.get(t.params.b_kind);
+ const resources = `
+${resourceA('resource_a', t.params.a_group, t.params.a_binding)}
+${resourceB('resource_b', t.params.b_group, t.params.b_binding)}
+`;
+ const expect =
+ t.params.a_group !== t.params.b_group || t.params.a_binding !== t.params.b_binding;
+
+ if (t.params.usage === 'direct') {
+ const code = `
+${resources}
+${declareEntrypoint('main', t.params.stage, '_ = resource_a; _ = resource_b;')}
+`;
+ t.expectCompileResult(expect, code);
+ } else {
+ const code = `
+${resources}
+fn use_a() { _ = resource_a; }
+fn use_b() { _ = resource_b; }
+${declareEntrypoint('main', t.params.stage, 'use_a(); use_b();')}
+`;
+ t.expectCompileResult(expect, code);
+ }
+});
+
+g.test('different_entry_points').
+desc(
+ `Test that resources may use the same binding points if exclusively accessed by different entry points.`
+).
+params((u) =>
+u.
+combine('a_stage', ['vertex', 'fragment', 'compute']).
+combine('b_stage', ['vertex', 'fragment', 'compute']).
+combine('a_kind', kResourceKindsA).
+combine('b_kind', kResourceKindsB).
+combine('usage', ['direct', 'transitive']).
+beginSubcases()
+).
+fn((t) => {
+ const resourceA = kResourceEmitters.get(t.params.a_kind);
+ const resourceB = kResourceEmitters.get(t.params.b_kind);
+ const resources = `
+${resourceA('resource_a', /* group */0, /* binding */0)}
+${resourceB('resource_b', /* group */0, /* binding */0)}
+`;
+ const expect = true; // Binding reuse between different entry points is fine.
+
+ if (t.params.usage === 'direct') {
+ const code = `
+${resources}
+${declareEntrypoint('main_a', t.params.a_stage, '_ = resource_a;')}
+${declareEntrypoint('main_b', t.params.b_stage, '_ = resource_b;')}
+`;
+ t.expectCompileResult(expect, code);
+ } else {
+ const code = `
+${resources}
+fn use_a() { _ = resource_a; }
+fn use_b() { _ = resource_b; }
+${declareEntrypoint('main_a', t.params.a_stage, 'use_a();')}
+${declareEntrypoint('main_b', t.params.b_stage, 'use_b();')}
+`;
+ t.expectCompileResult(expect, code);
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/id.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/id.spec.js
new file mode 100644
index 0000000000..4faca0391a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/id.spec.js
@@ -0,0 +1,170 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for id`;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 = {
+ zero: {
+ src: `@id(0)`,
+ pass: true
+ },
+ one: {
+ src: `@id(1)`,
+ pass: true
+ },
+ hex: {
+ src: `@id(0x1)`,
+ pass: true
+ },
+ trailing_comma: {
+ src: `@id(1,)`,
+ pass: true
+ },
+ i32: {
+ src: `@id(1i)`,
+ pass: true
+ },
+ ui32: {
+ src: `@id(1u)`,
+ pass: true
+ },
+ largest: {
+ src: `@id(65535)`,
+ pass: true
+ },
+ newline: {
+ src: '@\nid(1)',
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */id(1)`,
+ pass: true
+ },
+ const_expr: {
+ src: `const z = 5;
+ const y = 2;
+ @id(z + y)`,
+ pass: true
+ },
+
+ misspelling: {
+ src: `@aid(1)`,
+ pass: false
+ },
+ empty: {
+ src: `@id()`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@id 1)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@id(1`,
+ pass: false
+ },
+ multi_value: {
+ src: `@id(1, 2)`,
+ pass: false
+ },
+ overide_expr: {
+ src: `override z = 5;
+ override y = 2;
+ @id(z + y)`,
+ pass: false
+ },
+ f32_literal: {
+ src: `@id(1.0)`,
+ pass: false
+ },
+ f32: {
+ src: `@id(1f)`,
+ pass: false
+ },
+ negative: {
+ src: `@id(-1)`,
+ pass: false
+ },
+ too_large: {
+ src: `@id(65536)`,
+ pass: false
+ },
+ no_params: {
+ src: `@id`,
+ pass: false
+ },
+ duplicate: {
+ src: `@id(1) @id(1)`,
+ pass: false
+ }
+};
+
+g.test('id').
+desc(`Test validation of id`).
+params((u) => u.combine('attr', keysOf(kTests))).
+fn((t) => {
+ const code = `
+${kTests[t.params.attr].src}
+override a = 4;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {}`;
+ t.expectCompileResult(kTests[t.params.attr].pass, code);
+});
+
+g.test('id_fp16').
+desc(`Test validation of id with fp16`).
+params((u) => u.combine('ext', ['', 'h'])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const code = `
+@id(1${t.params.ext})
+override a = 4;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {}`;
+ t.expectCompileResult(t.params.ext === '', code);
+});
+
+g.test('id_struct_member').
+desc(`Test validation of id with struct member`).
+params((u) => u.combine('id', ['@id(1) override', '@id(1)', ''])).
+fn((t) => {
+ const code = `
+struct S {
+ ${t.params.id} a: i32,
+}
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {}`;
+ t.expectCompileResult(t.params.id === '', code);
+});
+
+g.test('id_non_override').
+desc(`Test validation of id with non-override`).
+params((u) => u.combine('type', ['var', 'const', 'override'])).
+fn((t) => {
+ const code = `
+@id(1) ${t.params['type']} a = 4;
+
+@workgroup_size(1, 1, 1)
+@compute fn main() {}`;
+ t.expectCompileResult(t.params['type'] === 'override', code);
+});
+
+g.test('id_in_function').
+desc(`Test validation of id inside a function`).
+params((u) => u.combine('id', ['@id(1)', ''])).
+fn((t) => {
+ const code = `
+@workgroup_size(1, 1, 1)
+@compute fn main() {
+ ${t.params['id']} var a = 4;
+}`;
+ t.expectCompileResult(t.params['id'] === '', code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/interpolate.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/interpolate.spec.js
new file mode 100644
index 0000000000..ec9adc0129
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/interpolate.spec.js
@@ -0,0 +1,217 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for the interpolate attribute`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import { generateShader } from './util.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// List of valid interpolation attributes.
+const kValidInterpolationAttributes = new Set([
+'',
+'@interpolate(flat)',
+'@interpolate(perspective)',
+'@interpolate(perspective, center)',
+'@interpolate(perspective, centroid)',
+'@interpolate(perspective, sample)',
+'@interpolate(linear)',
+'@interpolate(linear, center)',
+'@interpolate(linear, centroid)',
+'@interpolate(linear, sample)']
+);
+
+g.test('type_and_sampling').
+desc(`Test that all combinations of interpolation type and sampling are validated correctly.`).
+params((u) =>
+u.
+combine('stage', ['vertex', 'fragment']).
+combine('io', ['in', 'out']).
+combine('use_struct', [true, false]).
+combine('type', [
+'',
+'flat',
+'perspective',
+'linear',
+'center', // Invalid as first param
+'centroid', // Invalid as first param
+'sample' // Invalid as first param
+]).
+combine('sampling', [
+'',
+'center',
+'centroid',
+'sample',
+'flat', // Invalid as second param
+'perspective', // Invalid as second param
+'linear' // Invalid as second param
+]).
+beginSubcases()
+).
+fn((t) => {
+ if (t.params.stage === 'vertex' && t.params.use_struct === false) {
+ t.skip('vertex output must include a position builtin, so must use a struct');
+ }
+
+ let interpolate = '';
+ if (t.params.type !== '' || t.params.sampling !== '') {
+ interpolate = '@interpolate(';
+ if (t.params.type !== '') {
+ interpolate += `${t.params.type}`;
+ }
+ if (t.params.sampling !== '') {
+ interpolate += `, ${t.params.sampling}`;
+ }
+ interpolate += `)`;
+ }
+ const code = generateShader({
+ attribute: '@location(0)' + interpolate,
+ type: 'f32',
+ stage: t.params.stage,
+ io: t.params.io,
+ use_struct: t.params.use_struct
+ });
+
+ t.expectCompileResult(kValidInterpolationAttributes.has(interpolate), code);
+});
+
+g.test('require_location').
+desc(`Test that the interpolate attribute is only accepted with user-defined IO.`).
+params((u) =>
+u.
+combine('stage', ['vertex', 'fragment']).
+combine('attribute', ['@location(0)', '@builtin(position)']).
+combine('use_struct', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ if (
+ t.params.stage === 'vertex' &&
+ t.params.use_struct === false &&
+ !t.params.attribute.includes('position'))
+ {
+ t.skip('vertex output must include a position builtin, so must use a struct');
+ }
+
+ const code = generateShader({
+ attribute: t.params.attribute + `@interpolate(flat)`,
+ type: 'vec4<f32>',
+ stage: t.params.stage,
+ io: t.params.stage === 'fragment' ? 'in' : 'out',
+ use_struct: t.params.use_struct
+ });
+ t.expectCompileResult(t.params.attribute === '@location(0)', code);
+});
+
+g.test('integral_types').
+desc(`Test that the implementation requires @interpolate(flat) for integral user-defined IO.`).
+params((u) =>
+u.
+combine('stage', ['vertex', 'fragment']).
+combine('type', ['i32', 'u32', 'vec2<i32>', 'vec4<u32>']).
+combine('use_struct', [true, false]).
+combine('attribute', kValidInterpolationAttributes).
+beginSubcases()
+).
+fn((t) => {
+ if (t.params.stage === 'vertex' && t.params.use_struct === false) {
+ t.skip('vertex output must include a position builtin, so must use a struct');
+ }
+
+ const code = generateShader({
+ attribute: '@location(0)' + t.params.attribute,
+ type: t.params.type,
+ stage: t.params.stage,
+ io: t.params.stage === 'vertex' ? 'out' : 'in',
+ use_struct: t.params.use_struct
+ });
+
+ t.expectCompileResult(t.params.attribute === '@interpolate(flat)', code);
+});
+
+g.test('duplicate').
+desc(`Test that the interpolate attribute can only be applied once.`).
+params((u) => u.combine('attr', ['', '@interpolate(flat)'])).
+fn((t) => {
+ const code = generateShader({
+ attribute: `@location(0) @interpolate(flat) ${t.params.attr}`,
+ type: 'vec4<f32>',
+ stage: 'fragment',
+ io: 'in',
+ use_struct: false
+ });
+ t.expectCompileResult(t.params.attr === '', code);
+});
+
+const kValidationTests = {
+ valid: {
+ src: `@interpolate(flat)`,
+ pass: true
+ },
+ no_space: {
+ src: `@interpolate(perspective,center)`,
+ pass: true
+ },
+ trailing_comma_one_arg: {
+ src: `@interpolate(flat,)`,
+ pass: true
+ },
+ trailing_comma_two_arg: {
+ src: `@interpolate(perspective, center,)`,
+ pass: true
+ },
+ newline: {
+ src: '@\ninterpolate(flat)',
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */interpolate(flat)`,
+ pass: true
+ },
+
+ no_params: {
+ src: `@interpolate()`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@interpolate flat)`,
+ pass: false
+ },
+ missing_value_and_left_paren: {
+ src: `@interpolate)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@interpolate(flat`,
+ pass: false
+ },
+ missing_parens: {
+ src: `@interpolate`,
+ pass: false
+ },
+ missing_comma: {
+ src: `@interpolate(perspective center)`,
+ pass: false
+ },
+ numeric: {
+ src: `@interpolate(1)`,
+ pass: false
+ },
+ numeric_second_param: {
+ src: `@interpolate(perspective, 1)`,
+ pass: false
+ }
+};
+
+g.test('interpolation_validation').
+desc(`Test validation of interpolation`).
+params((u) => u.combine('attr', keysOf(kValidationTests))).
+fn((t) => {
+ const code = `
+@vertex fn main(${kValidationTests[t.params.attr].src} @location(0) b: f32) ->
+ @builtin(position) vec4<f32> {
+ return vec4f(0);
+}`;
+ t.expectCompileResult(kValidationTests[t.params.attr].pass, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/invariant.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/invariant.spec.js
new file mode 100644
index 0000000000..567fabdaf6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/invariant.spec.js
@@ -0,0 +1,99 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for the invariant attribute`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import { kBuiltins } from './builtins.spec.js';
+import { generateShader } from './util.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ invariant: {
+ src: `@invariant`,
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */invariant`,
+ pass: true
+ },
+ split_line: {
+ src: '@\ninvariant',
+ pass: true
+ },
+ empty_parens: {
+ src: `@invariant()`,
+ pass: false
+ },
+ value: {
+ src: `@invariant(0)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@invariant(`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@invariant)`,
+ pass: false
+ },
+ duplicate: {
+ src: `@invariant @invariant`,
+ pass: false
+ }
+};
+
+g.test('parsing').
+desc(`Test parsing of the invariant attribute`).
+params((u) => u.combine('attr', keysOf(kTests))).
+fn((t) => {
+ const code = `
+ struct VertexOut {
+ @builtin(position) ${kTests[t.params.attr].src} position : vec4<f32>
+ };
+ @vertex
+ fn main() -> VertexOut {
+ return VertexOut();
+ }
+ `;
+ t.expectCompileResult(kTests[t.params.attr].pass, code);
+});
+
+g.test('valid_only_with_vertex_position_builtin').
+desc(`Test that the invariant attribute is only accepted with the vertex position builtin`).
+params((u) =>
+u.
+combineWithParams(kBuiltins).
+combine('use_struct', [true, false]).
+beginSubcases()
+).
+fn((t) => {
+ const code = generateShader({
+ attribute: `@builtin(${t.params.name}) @invariant`,
+ type: t.params.type,
+ stage: t.params.stage,
+ io: t.params.io,
+ use_struct: t.params.use_struct
+ });
+
+ t.expectCompileResult(t.params.name === 'position', code);
+});
+
+g.test('not_valid_on_user_defined_io').
+desc(`Test that the invariant attribute is not accepted on user-defined IO attributes.`).
+params((u) => u.combine('use_invariant', [true, false]).beginSubcases()).
+fn((t) => {
+ const invariant = t.params.use_invariant ? '@invariant' : '';
+ const code = `
+ struct VertexOut {
+ @location(0) ${invariant} loc0 : vec4<f32>,
+ @builtin(position) position : vec4<f32>,
+ };
+ @vertex
+ fn main() -> VertexOut {
+ return VertexOut();
+ }
+ `;
+ t.expectCompileResult(!t.params.use_invariant, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/locations.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/locations.spec.js
new file mode 100644
index 0000000000..7dd5492283
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/locations.spec.js
@@ -0,0 +1,382 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for entry point user-defined IO`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+import { generateShader } from './util.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidLocationTypes = new Set([
+'f16',
+'f32',
+'i32',
+'u32',
+'vec2<f32>',
+'vec2<i32>',
+'vec2<u32>',
+'vec3<f32>',
+'vec3<i32>',
+'vec3<u32>',
+'vec4<f32>',
+'vec4<i32>',
+'vec4<u32>',
+'vec2h',
+'vec2f',
+'vec2i',
+'vec2u',
+'vec3h',
+'vec3f',
+'vec3i',
+'vec3u',
+'vec4h',
+'vec4f',
+'vec4i',
+'vec4u',
+'MyAlias']
+);
+
+const kInvalidLocationTypes = new Set([
+'bool',
+'vec2<bool>',
+'vec3<bool>',
+'vec4<bool>',
+'mat2x2<f32>',
+'mat2x3<f32>',
+'mat2x4<f32>',
+'mat3x2<f32>',
+'mat3x3<f32>',
+'mat3x4<f32>',
+'mat4x2<f32>',
+'mat4x3<f32>',
+'mat4x4<f32>',
+'mat2x2f',
+'mat2x3f',
+'mat2x4f',
+'mat3x2f',
+'mat3x3f',
+'mat3x4f',
+'mat4x2f',
+'mat4x3f',
+'mat4x4f',
+'mat2x2h',
+'mat2x3h',
+'mat2x4h',
+'mat3x2h',
+'mat3x3h',
+'mat3x4h',
+'mat4x2h',
+'mat4x3h',
+'mat4x4h',
+'array<f32, 12>',
+'array<i32, 12>',
+'array<u32, 12>',
+'array<bool, 12>',
+'atomic<i32>',
+'atomic<u32>',
+'MyStruct',
+'texture_1d<i32>',
+'texture_2d<f32>',
+'texture_2d_array<i32>',
+'texture_3d<f32>',
+'texture_cube<u32>',
+'texture_cube_array<i32>',
+'texture_multisampled_2d<i32>',
+'texture_external',
+'texture_storage_1d<rgba8unorm, write>',
+'texture_storage_2d<rg32float, write>',
+'texture_storage_2d_array<r32float, write>',
+'texture_storage_3d<r32float, write>',
+'texture_depth_2d',
+'texture_depth_2d_array',
+'texture_depth_cube',
+'texture_depth_cube_array',
+'texture_depth_multisampled_2d',
+'sampler',
+'sampler_comparison']
+);
+
+g.test('stage_inout').
+desc(`Test validation of user-defined IO stage and in/out usage`).
+params((u) =>
+u.
+combine('use_struct', [true, false]).
+combine('target_stage', ['vertex', 'fragment', 'compute']).
+combine('target_io', ['in', 'out']).
+beginSubcases()
+).
+fn((t) => {
+ const code = generateShader({
+ attribute: '@location(0)',
+ type: 'f32',
+ stage: t.params.target_stage,
+ io: t.params.target_io,
+ use_struct: t.params.use_struct
+ });
+
+ // Expect to fail for compute shaders or when used as a non-struct vertex output (since the
+ // position built-in must also be specified).
+ const expectation =
+ t.params.target_stage === 'fragment' ||
+ t.params.target_stage === 'vertex' && (t.params.target_io === 'in' || t.params.use_struct);
+ t.expectCompileResult(expectation, code);
+});
+
+g.test('type').
+desc(`Test validation of user-defined IO types`).
+params((u) =>
+u.
+combine('use_struct', [true, false]).
+combine('type', new Set([...kValidLocationTypes, ...kInvalidLocationTypes])).
+beginSubcases()
+).
+beforeAllSubcases((t) => {
+ if (
+ t.params.type === 'f16' ||
+ (t.params.type.startsWith('mat') || t.params.type.startsWith('vec')) &&
+ t.params.type.endsWith('h'))
+ {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ let code = '';
+
+ if (
+ t.params.type === 'f16' ||
+ (t.params.type.startsWith('mat') || t.params.type.startsWith('vec')) &&
+ t.params.type.endsWith('h'))
+ {
+ code += 'enable f16;\n';
+ }
+
+ if (t.params.type === 'MyStruct') {
+ // Generate a struct that contains a valid type.
+ code += `struct MyStruct {
+ value : f32,
+ }
+ `;
+ }
+ if (t.params.type === 'MyAlias') {
+ code += 'alias MyAlias = i32;\n';
+ }
+
+ code += generateShader({
+ attribute: '@location(0) @interpolate(flat)',
+ type: t.params.type,
+ stage: 'fragment',
+ io: 'in',
+ use_struct: t.params.use_struct
+ });
+
+ t.expectCompileResult(kValidLocationTypes.has(t.params.type), code);
+});
+
+g.test('nesting').
+desc(`Test validation of nested user-defined IO`).
+params((u) =>
+u.
+combine('target_stage', ['vertex', 'fragment', '']).
+combine('target_io', ['in', 'out']).
+beginSubcases()
+).
+fn((t) => {
+ let code = '';
+
+ // Generate a struct that contains a valid type.
+ code += `struct Inner {
+ @location(0) value : f32,
+ }
+ struct Outer {
+ inner : Inner,
+ }
+ `;
+
+ code += generateShader({
+ attribute: '',
+ type: 'Outer',
+ stage: t.params.target_stage,
+ io: t.params.target_io,
+ use_struct: false
+ });
+
+ // Expect to pass only if the struct is not used for entry point IO.
+ t.expectCompileResult(t.params.target_stage === '', code);
+});
+
+g.test('duplicates').
+desc(`Test that duplicated user-defined IO attributes are validated.`).
+params((u) =>
+u
+// Place two @location(0) attributes onto the entry point function.
+// The function:
+// - has two non-struct parameters (`p1` and `p2`)
+// - has two struct parameters each with two members (`s1{a,b}` and `s2{a,b}`)
+// - returns a struct with two members (`ra` and `rb`)
+// By default, all of these user-defined IO variables will have unique location attributes.
+.combine('first', ['p1', 's1a', 's2a', 'ra']).
+combine('second', ['p2', 's1b', 's2b', 'rb']).
+beginSubcases()
+).
+fn((t) => {
+ const p1 = t.params.first === 'p1' ? '0' : '1';
+ const p2 = t.params.second === 'p2' ? '0' : '2';
+ const s1a = t.params.first === 's1a' ? '0' : '3';
+ const s1b = t.params.second === 's1b' ? '0' : '4';
+ const s2a = t.params.first === 's2a' ? '0' : '5';
+ const s2b = t.params.second === 's2b' ? '0' : '6';
+ const ra = t.params.first === 'ra' ? '0' : '1';
+ const rb = t.params.second === 'rb' ? '0' : '2';
+ const code = `
+ struct S1 {
+ @location(${s1a}) a : f32,
+ @location(${s1b}) b : f32,
+ };
+ struct S2 {
+ @location(${s2a}) a : f32,
+ @location(${s2b}) b : f32,
+ };
+ struct R {
+ @location(${ra}) a : f32,
+ @location(${rb}) b : f32,
+ };
+ @fragment
+ fn main(@location(${p1}) p1 : f32,
+ @location(${p2}) p2 : f32,
+ s1 : S1,
+ s2 : S2,
+ ) -> R {
+ return R();
+ }
+ `;
+
+ // The test should fail if both @location(0) attributes are on the input parameters or
+ // structures, or it they are both on the output struct. Otherwise it should pass.
+ const firstIsRet = t.params.first === 'ra';
+ const secondIsRet = t.params.second === 'rb';
+ const expectation = firstIsRet !== secondIsRet;
+ t.expectCompileResult(expectation, code);
+});
+
+const kValidationTests = {
+ zero: {
+ src: `@location(0)`,
+ pass: true
+ },
+ one: {
+ src: `@location(1)`,
+ pass: true
+ },
+ extra_comma: {
+ src: `@location(1,)`,
+ pass: true
+ },
+ i32: {
+ src: `@location(1i)`,
+ pass: true
+ },
+ u32: {
+ src: `@location(1u)`,
+ pass: true
+ },
+ hex: {
+ src: `@location(0x1)`,
+ pass: true
+ },
+ const_expr: {
+ src: `@location(a + b)`,
+ pass: true
+ },
+ max: {
+ src: `@location(2147483647)`,
+ pass: true
+ },
+ newline: {
+ src: '@\nlocation(1)',
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */location(1)`,
+ pass: true
+ },
+
+ misspelling: {
+ src: `@mlocation(1)`,
+ pass: false
+ },
+ no_parens: {
+ src: `@location`,
+ pass: false
+ },
+ empty_params: {
+ src: `@location()`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@location 1)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@location(1`,
+ pass: false
+ },
+ extra_params: {
+ src: `@location(1, 2)`,
+ pass: false
+ },
+ f32: {
+ src: `@location(1f)`,
+ pass: false
+ },
+ f32_literal: {
+ src: `@location(1.0)`,
+ pass: false
+ },
+ negative: {
+ src: `@location(-1)`,
+ pass: false
+ },
+ override_expr: {
+ src: `@location(z + y)`,
+ pass: false
+ },
+ vec: {
+ src: `@location(vec2(1,1))`,
+ pass: false
+ }
+};
+g.test('validation').
+desc(`Test validation of location`).
+params((u) => u.combine('attr', keysOf(kValidationTests))).
+fn((t) => {
+ const code = `
+const a = 5;
+const b = 6;
+override z = 7;
+override y = 8;
+
+@vertex fn main(
+ ${kValidationTests[t.params.attr].src} res: f32
+) -> @builtin(position) vec4f {
+ return vec4f(0);
+}`;
+ t.expectCompileResult(kValidationTests[t.params.attr].pass, code);
+});
+
+g.test('location_fp16').
+desc(`Test validation of location with fp16`).
+params((u) => u.combine('ext', ['', 'h'])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const code = `
+
+@vertex fn main(
+ @location(1${t.params.ext}) res: f32
+) -> @builtin(position) vec4f {
+ return vec4f();
+}`;
+ t.expectCompileResult(t.params.ext === '', code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/size.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/size.spec.js
new file mode 100644
index 0000000000..74caf19e6d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/size.spec.js
@@ -0,0 +1,212 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for size`;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 kSizeTests = {
+ valid: {
+ src: `@size(4)`,
+ pass: true
+ },
+ non_align_size: {
+ src: `@size(5)`,
+ pass: true
+ },
+ i32: {
+ src: `@size(4i)`,
+ pass: true
+ },
+ u32: {
+ src: `@size(4u)`,
+ pass: true
+ },
+ constant: {
+ src: `@size(z)`,
+ pass: true
+ },
+ trailing_comma: {
+ src: `@size(4,)`,
+ pass: true
+ },
+ hex: {
+ src: `@size(0x4)`,
+ pass: true
+ },
+ whitespace: {
+ src: '@\nsize(4)',
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */size(4)`,
+ pass: true
+ },
+ large: {
+ src: `@size(2147483647)`,
+ pass: true
+ },
+
+ misspelling: {
+ src: `@msize(4)`,
+ pass: false
+ },
+ no_value: {
+ src: `@size()`,
+ pass: false
+ },
+ missing_left_paren: {
+ src: `@size 4)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@size(4`,
+ pass: false
+ },
+ missing_parens: {
+ src: `@size`,
+ pass: false
+ },
+ multiple_values: {
+ src: `@size(4, 8)`,
+ pass: false
+ },
+ override: {
+ src: `@size(over)`,
+ pass: false
+ },
+ zero: {
+ src: `@size(0)`,
+ pass: false
+ },
+ negative: {
+ src: `@size(-4)`,
+ pass: false
+ },
+ f32_literal: {
+ src: `@size(4.0)`,
+ pass: false
+ },
+ f32: {
+ src: `@size(4f)`,
+ pass: false
+ },
+ duplicate: {
+ src: `@size(4) @size(8)`,
+ pass: false
+ },
+ too_small: {
+ src: `@size(1)`,
+ pass: false
+ }
+};
+
+g.test('size').
+desc(`Test validation of ize`).
+params((u) => u.combine('attr', keysOf(kSizeTests))).
+fn((t) => {
+ const code = `
+override over: i32 = 4;
+const z: i32 = 4;
+
+struct S {
+ ${kSizeTests[t.params.attr].src} a: f32,
+};
+@group(0) @binding(0)
+var<storage> a: S;
+
+@workgroup_size(1)
+@compute fn main() {
+ _ = a;
+}`;
+ t.expectCompileResult(kSizeTests[t.params.attr].pass, code);
+});
+
+g.test('size_fp16').
+desc(`Test validation of size with fp16`).
+params((u) => u.combine('ext', ['', 'h'])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const code = `
+struct S {
+ @size(4${t.params.ext}) a: f32,
+}
+@group(0) @binding(0)
+var<storage> a: S;
+
+@workgroup_size(1)
+@compute fn main() {
+ _ = a;
+}`;
+ t.expectCompileResult(t.params.ext === '', code);
+});
+
+const kNonStructTests = {
+ control: {
+ mod_src: ``,
+ func_src: ``,
+ size: 0,
+ pass: true
+ },
+ struct: {
+ mod_src: `struct S { a: f32 }`,
+ func_src: ``,
+ size: 4,
+ pass: false
+ },
+ constant: {
+ mod_src: `const a: f32 = 4.0;`,
+ func_src: ``,
+ size: 4,
+ pass: false
+ },
+ vec: {
+ mod_src: ``,
+ func_src: `vec4<f32>`,
+ size: 16,
+ pass: false
+ },
+ mat: {
+ mod_src: ``,
+ func_src: `mat4x4<f32>`,
+ size: 64,
+ pass: false
+ },
+ array: {
+ mod_src: ``,
+ func_src: `array<f32, 4>`,
+ size: 16,
+ pass: false
+ },
+ scalar: {
+ mod_src: ``,
+ func_src: `f32`,
+ size: 4,
+ pass: false
+ }
+};
+
+g.test('size_non_struct').
+desc(`Test validation of size outside of a struct`).
+params((u) => u.combine('attr', keysOf(kNonStructTests))).
+fn((t) => {
+ const data = kNonStructTests[t.params.attr];
+ let code = '';
+ if (data.mod_src !== '') {
+ code += `@size(${data.size}) ${data.mod_src}`;
+ }
+
+ code += `
+@workgroup_size(1)
+@compute fn main() {
+`;
+ if (data.func_src !== '') {
+ code += `@size(${data.size}) var a: ${data.func_src};`;
+ }
+ code += '}';
+
+ t.expectCompileResult(data.pass, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/util.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/util.js
new file mode 100644
index 0000000000..934016eaea
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/util.js
@@ -0,0 +1,196 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Generate an entry point that uses an entry point IO variable.
+ *
+ * @param {Object} params
+ * @param params.attribute The entry point IO attribute.
+ * @param params.type The type to use for the entry point IO variable.
+ * @param params.stage The shader stage.
+ * @param params.io An "in|out" string specifying whether the entry point IO is an input or an output.
+ * @param params.use_struct True to wrap the entry point IO in a struct.
+ * @returns The generated shader code.
+ */export function generateShader({ attribute,
+ type,
+ stage,
+ io,
+ use_struct
+
+
+
+
+
+
+}) {
+ let code = '';
+
+ if (use_struct) {
+ // Generate a struct that wraps the entry point IO variable.
+ code += 'struct S {\n';
+ code += ` ${attribute} value : ${type},\n`;
+ if (stage === 'vertex' && io === 'out' && !attribute.includes('builtin(position)')) {
+ // Add position builtin for vertex outputs.
+ code += ` @builtin(position) position : vec4<f32>,\n`;
+ }
+ code += '};\n\n';
+ }
+
+ if (stage !== '') {
+ // Generate the entry point attributes.
+ code += `@${stage}`;
+ if (stage === 'compute') {
+ code += ' @workgroup_size(1)';
+ }
+ }
+
+ // Generate the entry point parameter and return type.
+ let param = '';
+ let retType = '';
+ let retVal = '';
+ if (io === 'in') {
+ if (use_struct) {
+ param = `in : S`;
+ } else {
+ param = `${attribute} value : ${type}`;
+ }
+
+ // Vertex shaders must always return `@builtin(position)`.
+ if (stage === 'vertex') {
+ retType = `-> @builtin(position) vec4<f32>`;
+ retVal = `return vec4<f32>();`;
+ }
+ } else if (io === 'out') {
+ if (use_struct) {
+ retType = '-> S';
+ retVal = `return S();`;
+ } else {
+ retType = `-> ${attribute} ${type}`;
+ retVal = `return ${type}();`;
+ }
+ }
+
+ code += `
+ fn main(${param}) ${retType} {
+ ${retVal}
+ }
+ `;
+
+ return code;
+}
+
+/**
+ * ResourceDeclarationEmitter is a function that emits the WGSL declaring a resource variable with
+ * the given group, binding and name.
+ */
+
+
+/** Helper function for emitting a resource declaration's group and binding attributes */
+function groupAndBinding(group, binding) {
+ return (
+ `${group !== undefined ? `@group(${group})` : '/* no group */'} ` +
+ `${binding !== undefined ? `@binding(${binding})` : '/* no binding */'}`);
+
+}
+
+/** Helper function for emitting a resource declaration for the given type */
+function basicEmitter(type) {
+ return (name, group, binding) =>
+ `${groupAndBinding(group, binding)} var ${name} : ${type};\n`;
+}
+
+/** Map of resource declaration name, to an emitter. */
+export const kResourceEmitters = new Map([
+['texture_1d', basicEmitter('texture_1d<i32>')],
+['texture_2d', basicEmitter('texture_2d<i32>')],
+['texture_2d_array', basicEmitter('texture_2d_array<f32>')],
+['texture_3d', basicEmitter('texture_3d<i32>')],
+['texture_cube', basicEmitter('texture_cube<u32>')],
+['texture_cube_array', basicEmitter('texture_cube_array<u32>')],
+['texture_multisampled_2d', basicEmitter('texture_multisampled_2d<i32>')],
+['texture_external', basicEmitter('texture_external')],
+['texture_storage_1d', basicEmitter('texture_storage_1d<rgba8unorm, write>')],
+['texture_storage_2d', basicEmitter('texture_storage_2d<rgba8sint, write>')],
+['texture_storage_2d_array', basicEmitter('texture_storage_2d_array<r32uint, write>')],
+['texture_storage_3d', basicEmitter('texture_storage_3d<rg32uint, write>')],
+['texture_depth_2d', basicEmitter('texture_depth_2d')],
+['texture_depth_2d_array', basicEmitter('texture_depth_2d_array')],
+['texture_depth_cube', basicEmitter('texture_depth_cube')],
+['texture_depth_cube_array', basicEmitter('texture_depth_cube_array')],
+['texture_depth_multisampled_2d', basicEmitter('texture_depth_multisampled_2d')],
+['sampler', basicEmitter('sampler')],
+['sampler_comparison', basicEmitter('sampler_comparison')],
+[
+'uniform',
+(name, group, binding) =>
+`${groupAndBinding(group, binding)} var<uniform> ${name} : array<vec4<f32>, 16>;\n`],
+
+[
+'storage',
+(name, group, binding) =>
+`${groupAndBinding(group, binding)} var<storage> ${name} : array<vec4<f32>, 16>;\n`]]
+
+);
+
+/** All possible resource types for use as test permutations. */
+export const kResourceKindsAll = [
+'texture_1d',
+'texture_2d',
+'texture_2d_array',
+'texture_3d',
+'texture_cube',
+'texture_cube_array',
+'texture_multisampled_2d',
+'texture_external',
+'texture_storage_1d',
+'texture_storage_2d',
+'texture_storage_2d_array',
+'texture_storage_3d',
+'texture_depth_2d',
+'texture_depth_2d_array',
+'texture_depth_cube',
+'texture_depth_cube_array',
+'texture_depth_multisampled_2d',
+'sampler',
+'sampler_comparison',
+'uniform',
+'storage'];
+
+
+/** A small selection of resource declaration names, which can be used in test permutations */
+export const kResourceKindsA = ['storage', 'texture_2d', 'texture_external', 'uniform'];
+
+/** A small selection of resource declaration names, which can be used in test permutations */
+export const kResourceKindsB = ['texture_3d', 'texture_storage_1d', 'uniform'];
+
+/** An enumerator of shader stages */
+
+
+/**
+ * declareEntrypoint emits the WGSL to declare an entry point with the given name, stage and body.
+ * The generated function will have an appropriate return type and return statement, so that `body`
+ * does not have to change between stage.
+ * @param name the entry point function name
+ * @param stage the entry point stage
+ * @param body the body of the function (excluding any automatically suffixed return statements)
+ * @returns the WGSL string for the entry point
+ */
+export function declareEntrypoint(name, stage, body) {
+ switch (stage) {
+ case 'vertex':
+ return `@vertex
+fn ${name}() -> @builtin(position) vec4f {
+ ${body}
+ return vec4f();
+}`;
+ case 'fragment':
+ return `@fragment
+fn ${name}() {
+ ${body}
+}`;
+ case 'compute':
+ return `@compute @workgroup_size(1)
+fn ${name}() {
+ ${body}
+}`;
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/workgroup_size.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/workgroup_size.spec.js
new file mode 100644
index 0000000000..4c8396bff0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_io/workgroup_size.spec.js
@@ -0,0 +1,300 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for workgroup_size`;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 kWorkgroupSizeTests = {
+ x_only_float: {
+ src: `@workgroup_size(8f)`,
+ pass: false
+ },
+ xy_only_float: {
+ src: `@workgroup_size(8, 8f)`,
+ pass: false
+ },
+ xyz_float: {
+ src: `@workgroup_size(8, 8, 8f)`,
+ pass: false
+ },
+ x_only_float_literal: {
+ src: `@workgroup_size(8.0)`,
+ pass: false
+ },
+ xy_only_float_literal: {
+ src: `@workgroup_size(8, 8.0)`,
+ pass: false
+ },
+ xyz_float_literal: {
+ src: `@workgroup_size(8, 8, 8.0)`,
+ pass: false
+ },
+ empty: {
+ src: `@workgroup_size()`,
+ pass: false
+ },
+ empty_x: {
+ src: `@workgroup_size(, 8)`,
+ pass: false
+ },
+ empty_y: {
+ src: `@workgroup_size(8, , 8)`,
+ pass: false
+ },
+ invalid_entry: {
+ src: `@workgroup_size(let)`,
+ pass: false
+ },
+
+ x_only_abstract: {
+ src: `@workgroup_size(8)`,
+ pass: true
+ },
+ xy_only_abstract: {
+ src: `@workgroup_size(8, 8)`,
+ pass: true
+ },
+ xyz_abstract: {
+ src: `@workgroup_size(8, 8, 8)`,
+ pass: true
+ },
+ x_only_unsigned: {
+ src: `@workgroup_size(8u)`,
+ pass: true
+ },
+ xy_only_unsigned: {
+ src: `@workgroup_size(8u, 8u)`,
+ pass: true
+ },
+ xyz_unsigned: {
+ src: `@workgroup_size(8u, 8u, 8u)`,
+ pass: true
+ },
+ x_only_signed: {
+ src: `@workgroup_size(8i)`,
+ pass: true
+ },
+ xy_only_signed: {
+ src: `@workgroup_size(8i, 8i)`,
+ pass: true
+ },
+ xyz_signed: {
+ src: `@workgroup_size(8i, 8i, 8i)`,
+ pass: true
+ },
+ x_only_hex: {
+ src: `@workgroup_size(0x1)`,
+ pass: true
+ },
+ xy_only_hex: {
+ src: `@workgroup_size(0x1, 0x1)`,
+ pass: true
+ },
+ xyz_hex: {
+ src: `@workgroup_size(0x1, 0x1, 0x1)`,
+ pass: true
+ },
+
+ const_expr: {
+ src: `const a = 4;
+ const b = 5;
+ @workgroup_size(a, b, a + b)`,
+ pass: true
+ },
+
+ override: {
+ src: `@id(42) override block_width = 12u;
+@workgroup_size(block_width)`,
+ pass: true
+ },
+ override_no_default: {
+ src: `override block_width: i32;
+@workgroup_size(block_width)`,
+ pass: true
+ },
+
+ trailing_comma_x: {
+ src: `@workgroup_size(8, )`,
+ pass: true
+ },
+ trailing_comma_y: {
+ src: `@workgroup_size(8, 8,)`,
+ pass: true
+ },
+ trailing_comma_z: {
+ src: `@workgroup_size(8, 8, 8,)`,
+ pass: true
+ },
+
+ override_expr: {
+ src: `override a = 5;
+ override b = 6;
+ @workgroup_size(a, b, a + b)`,
+ pass: true
+ },
+
+ // Mixed abstract is ok
+ mixed_abstract_signed: {
+ src: `@workgroup_size(8, 8i)`,
+ pass: true
+ },
+ mixed_abstract_unsigned: {
+ src: `@workgroup_size(8u, 8)`,
+ pass: true
+ },
+ // Mixed signed and unsigned is not
+ mixed_signed_unsigned: {
+ src: `@workgroup_size(8i, 8i, 8u)`,
+ pass: false
+ },
+
+ zero_x: {
+ src: `@workgroup_size(0)`,
+ pass: false
+ },
+ zero_y: {
+ src: `@workgroup_size(8, 0)`,
+ pass: false
+ },
+ zero_z: {
+ src: `@workgroup_size(8, 8, 0)`,
+ pass: false
+ },
+ negative_x: {
+ src: `@workgroup_size(-8)`,
+ pass: false
+ },
+ negative_y: {
+ src: `@workgroup_size(8, -8)`,
+ pass: false
+ },
+ negative_z: {
+ src: `@workgroup_size(8, 8, -8)`,
+ pass: false
+ },
+
+ max_values: {
+ src: `@workgroup_size(256, 256, 64)`,
+ pass: true
+ },
+
+ missing_left_paren: {
+ src: `@workgroup_size 1, 2, 3)`,
+ pass: false
+ },
+ missing_right_paren: {
+ src: `@workgroup_size(1, 2, 3`,
+ pass: false
+ },
+ misspelling: {
+ src: `@aworkgroup_size(1)`,
+ pass: false
+ },
+ no_params: {
+ src: `@workgroup_size`,
+ pass: false
+ },
+ multi_line: {
+ src: '@\nworkgroup_size(1)',
+ pass: true
+ },
+ comment: {
+ src: `@/* comment */workgroup_size(1)`,
+ pass: true
+ },
+
+ mix_ux: {
+ src: `@workgroup_size(1u, 1i, 1i)`,
+ pass: false
+ },
+ mix_uy: {
+ src: `@workgroup_size(1i, 1u, 1i)`,
+ pass: false
+ },
+ mix_uz: {
+ src: `@workgroup_size(1i, 1i, 1u)`,
+ pass: false
+ },
+
+ duplicate: {
+ src: `@workgroup_size(1)
+@workgroup_size(2, 2, 2)`,
+ pass: false
+ }
+};
+g.test('workgroup_size').
+desc(`Test validation of workgroup_size`).
+params((u) => u.combine('attr', keysOf(kWorkgroupSizeTests))).
+fn((t) => {
+ const code = `
+${kWorkgroupSizeTests[t.params.attr].src}
+@compute fn main() {}`;
+ t.expectCompileResult(kWorkgroupSizeTests[t.params.attr].pass, code);
+});
+
+g.test('workgroup_size_fragment_shader').
+desc(`Test validation of workgroup_size on a fragment shader`).
+fn((t) => {
+ const code = `
+@workgroup_size(1)
+@fragment fn main(@builtin(position) pos: vec4<f32>) {}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('workgroup_size_vertex_shader').
+desc(`Test validation of workgroup_size on a vertex shader`).
+fn((t) => {
+ const code = `
+@workgroup_size(1)
+@vertex fn main() -> @builtin(position) vec4<f32> {}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('workgroup_size_function').
+desc(`Test validation of workgroup_size on user function`).
+fn((t) => {
+ const code = `
+@workgroup_size(1)
+fn my_func() {}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('workgroup_size_const').
+desc(`Test validation of workgroup_size on a const`).
+fn((t) => {
+ const code = `
+@workgroup_size(1)
+const a : i32 = 4;
+
+fn my_func() {}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('workgroup_size_var').
+desc(`Test validation of workgroup_size on a var`).
+fn((t) => {
+ const code = `
+@workgroup_size(1)
+@group(1) @binding(1)
+var<storage> a: i32;
+
+fn my_func() {
+ _ = a;
+}`;
+ t.expectCompileResult(false, code);
+});
+
+g.test('workgroup_size_fp16').
+desc(`Test validation of workgroup_size with fp16`).
+params((u) => u.combine('ext', ['', 'h'])).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+}).
+fn((t) => {
+ const code = `
+@workgroup_size(1${t.params.ext})
+@compute fn main() {}`;
+ t.expectCompileResult(t.params.ext === '', code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_validation_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_validation_test.js
new file mode 100644
index 0000000000..efbfe6e009
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/shader_validation_test.js
@@ -0,0 +1,177 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { keysOf } from '../../../common/util/data_tables.js';import { ErrorWithExtra } from '../../../common/util/util.js';import { GPUTest } from '../../gpu_test.js';
+
+/**
+ * Base fixture for WGSL shader validation tests.
+ */
+export class ShaderValidationTest extends GPUTest {
+ /**
+ * Add a test expectation for whether a createShaderModule call succeeds or not.
+ *
+ * @example
+ * ```ts
+ * t.expectCompileResult(true, `wgsl code`); // Expect success
+ * t.expectCompileResult(false, `wgsl code`); // Expect validation error with any error string
+ * ```
+ */
+ expectCompileResult(expectedResult, code) {
+ let shaderModule;
+ this.expectGPUError(
+ 'validation',
+ () => {
+ shaderModule = this.device.createShaderModule({ code });
+ },
+ expectedResult !== true
+ );
+
+ const error = new ErrorWithExtra('', () => ({ shaderModule }));
+ this.eventualAsyncExpectation(async () => {
+ const compilationInfo = await shaderModule.getCompilationInfo();
+
+ // MAINTENANCE_TODO: Pretty-print error messages with source context.
+ const messagesLog = compilationInfo.messages.
+ map((m) => `${m.lineNum}:${m.linePos}: ${m.type}: ${m.message}`).
+ join('\n');
+ error.extra.compilationInfo = compilationInfo;
+
+ if (compilationInfo.messages.some((m) => m.type === 'error')) {
+ if (expectedResult) {
+ error.message = `Unexpected compilationInfo 'error' message.\n` + messagesLog;
+ this.rec.validationFailed(error);
+ } else {
+ error.message = `Found expected compilationInfo 'error' message.\n` + messagesLog;
+ this.rec.debug(error);
+ }
+ } else {
+ if (!expectedResult) {
+ error.message = `Missing expected compilationInfo 'error' message.\n` + messagesLog;
+ this.rec.validationFailed(error);
+ } else {
+ error.message = `No compilationInfo 'error' messages, as expected.\n` + messagesLog;
+ this.rec.debug(error);
+ }
+ }
+ });
+ }
+
+ /**
+ * Add a test expectation for whether a createShaderModule call issues a warning.
+ *
+ * @example
+ * ```ts
+ * t.expectCompileWarning(true, `wgsl code`); // Expect compile success and any warning message
+ * t.expectCompileWarning(false, `wgsl code`); // Expect compile success and no warning messages
+ * ```
+ */
+ expectCompileWarning(expectWarning, code) {
+ let shaderModule;
+ this.expectGPUError(
+ 'validation',
+ () => {
+ shaderModule = this.device.createShaderModule({ code });
+ },
+ false
+ );
+
+ const error = new ErrorWithExtra('', () => ({ shaderModule }));
+ this.eventualAsyncExpectation(async () => {
+ const compilationInfo = await shaderModule.getCompilationInfo();
+
+ // MAINTENANCE_TODO: Pretty-print error messages with source context.
+ const messagesLog = compilationInfo.messages.
+ map((m) => `${m.lineNum}:${m.linePos}: ${m.type}: ${m.message}`).
+ join('\n');
+ error.extra.compilationInfo = compilationInfo;
+
+ if (compilationInfo.messages.some((m) => m.type === 'warning')) {
+ if (expectWarning) {
+ error.message = `No 'warning' message as expected.\n` + messagesLog;
+ this.rec.debug(error);
+ } else {
+ error.message = `Missing expected compilationInfo 'warning' message.\n` + messagesLog;
+ this.rec.validationFailed(error);
+ }
+ } else {
+ if (expectWarning) {
+ error.message = `Missing expected 'warning' message.\n` + messagesLog;
+ this.rec.validationFailed(error);
+ } else {
+ error.message = `Found a 'warning' message as expected.\n` + messagesLog;
+ this.rec.debug(error);
+ }
+ }
+ });
+ }
+
+ /**
+ * Add a test expectation for whether a createComputePipeline call succeeds or not.
+ */
+ expectPipelineResult(args)
+
+
+
+
+
+
+
+
+ {
+ const phonies = [];
+
+ if (args.constants !== undefined) {
+ phonies.push(...keysOf(args.constants).map((c) => `_ = ${c};`));
+ }
+ if (args.reference !== undefined) {
+ phonies.push(...args.reference.map((c) => `_ = ${c};`));
+ }
+
+ const code =
+ args.code +
+ `
+@compute @workgroup_size(1)
+fn main() {
+ ${phonies.join('\n')}
+}`;
+
+ let shaderModule;
+ this.expectGPUError(
+ 'validation',
+ () => {
+ shaderModule = this.device.createShaderModule({ code });
+ },
+ false
+ );
+
+ this.expectGPUError(
+ 'validation',
+ () => {
+ this.device.createComputePipeline({
+ layout: 'auto',
+ compute: { module: shaderModule, entryPoint: 'main', constants: args.constants }
+ });
+ },
+ !args.expectedResult
+ );
+ }
+
+ /**
+ * Wraps the code fragment into an entry point.
+ *
+ * @example
+ * ```ts
+ * t.wrapInEntryPoint(`var i = 0;`);
+ * ```
+ */
+ wrapInEntryPoint(code, enabledExtensions = []) {
+ const enableDirectives = enabledExtensions.map((x) => `enable ${x};`).join('\n ');
+
+ return `
+ ${enableDirectives}
+
+ @compute @workgroup_size(1)
+ fn main() {
+ ${code}
+ }`;
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/alias.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/alias.spec.js
new file mode 100644
index 0000000000..012652df4b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/alias.spec.js
@@ -0,0 +1,123 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for type aliases
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('no_direct_recursion').
+desc('Test that direct recursion of type aliases is rejected').
+params((u) => u.combine('target', ['i32', 'T'])).
+fn((t) => {
+ const wgsl = `alias T = ${t.params.target};`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion').
+desc('Test that indirect recursion of type aliases is rejected').
+params((u) => u.combine('target', ['i32', 'S'])).
+fn((t) => {
+ const wgsl = `
+alias S = T;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_vector_element').
+desc('Test that indirect recursion of type aliases via vector element types is rejected').
+params((u) => u.combine('target', ['i32', 'V'])).
+fn((t) => {
+ const wgsl = `
+alias V = vec4<T>;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_matrix_element').
+desc('Test that indirect recursion of type aliases via matrix element types is rejected').
+params((u) => u.combine('target', ['f32', 'M'])).
+fn((t) => {
+ const wgsl = `
+alias M = mat4x4<T>;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'f32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_array_element').
+desc('Test that indirect recursion of type aliases via array element types is rejected').
+params((u) => u.combine('target', ['i32', 'A'])).
+fn((t) => {
+ const wgsl = `
+alias A = array<T, 4>;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_array_size').
+desc('Test that indirect recursion of type aliases via array size expressions is rejected').
+params((u) => u.combine('target', ['i32', 'A'])).
+fn((t) => {
+ const wgsl = `
+alias A = array<i32, T(1)>;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_atomic').
+desc('Test that indirect recursion of type aliases via atomic types is rejected').
+params((u) => u.combine('target', ['i32', 'A'])).
+fn((t) => {
+ const wgsl = `
+alias A = atomic<T>;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_ptr_store_type').
+desc('Test that indirect recursion of type aliases via pointer store types is rejected').
+params((u) => u.combine('target', ['i32', 'P'])).
+fn((t) => {
+ const wgsl = `
+alias P = ptr<function, T>;
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_struct_member').
+desc('Test that indirect recursion of type aliases via struct members is rejected').
+params((u) => u.combine('target', ['i32', 'S'])).
+fn((t) => {
+ const wgsl = `
+struct S {
+ a : T
+}
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_struct_attribute').
+desc('Test that indirect recursion of type aliases via struct members is rejected').
+params((u) =>
+u //
+.combine('target', ['i32', 'S']).
+combine('attribute', ['align', 'location', 'size'])
+).
+fn((t) => {
+ const wgsl = `
+struct S {
+ @${t.params.attribute}(T(4)) a : f32
+}
+alias T = ${t.params.target};
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/struct.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/struct.spec.js
new file mode 100644
index 0000000000..80a6064232
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/struct.spec.js
@@ -0,0 +1,99 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for struct types
+`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('no_direct_recursion').
+desc('Test that direct recursion of structures is rejected').
+params((u) => u.combine('target', ['i32', 'S'])).
+fn((t) => {
+ const wgsl = `
+struct S {
+ a : ${t.params.target}
+}`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion').
+desc('Test that indirect recursion of structures is rejected').
+params((u) => u.combine('target', ['i32', 'S'])).
+fn((t) => {
+ const wgsl = `
+struct S {
+ a : T
+}
+struct T {
+ a : ${t.params.target}
+}`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_array_element').
+desc('Test that indirect recursion of structures via array element types is rejected').
+params((u) => u.combine('target', ['i32', 'S'])).
+fn((t) => {
+ const wgsl = `
+struct S {
+ a : array<${t.params.target}, 4>
+}
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+});
+
+g.test('no_indirect_recursion_via_array_size').
+desc('Test that indirect recursion of structures via array size expressions is rejected').
+params((u) => u.combine('target', ['S1', 'S2'])).
+fn((t) => {
+ const wgsl = `
+struct S1 {
+ a : i32,
+}
+struct S2 {
+ a : i32,
+ b : array<i32, ${t.params.target}().a + 1>,
+}
+`;
+ t.expectCompileResult(t.params.target === 'S1', wgsl);
+});
+
+g.test('no_indirect_recursion_via_struct_attribute').
+desc('Test that indirect recursion of structures via struct members is rejected').
+params((u) =>
+u //
+.combine('target', ['S1', 'S2']).
+combine('attribute', ['align', 'location', 'size'])
+).
+fn((t) => {
+ const wgsl = `
+struct S1 {
+ a : i32
+}
+struct S2 {
+ @${t.params.attribute}(${t.params.target}(4).a) a : i32
+}
+`;
+ t.expectCompileResult(t.params.target === 'S1', wgsl);
+});
+
+g.test('no_indirect_recursion_via_struct_member_nested_in_alias').
+desc(
+ `Test that indirect recursion of structures via struct members is rejected when the member type
+ is an alias that contains the structure`
+).
+params((u) => u.combine('target', ['i32', 'A'])).
+fn((t) => {
+ const wgsl = `
+alias A = array<S2, 4>;
+struct S1 {
+ a : ${t.params.target}
+}
+struct S2 {
+ a : S1
+}
+`;
+ t.expectCompileResult(t.params.target === 'i32', wgsl);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/vector.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/vector.spec.js
new file mode 100644
index 0000000000..7002bde624
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/types/vector.spec.js
@@ -0,0 +1,78 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Validation tests for vector 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 kCases = {
+ // Valid vector types
+ vec2_bool: { wgsl: 'alias T = vec2<bool>;', ok: true },
+ vec3_bool: { wgsl: 'alias T = vec3<bool>;', ok: true },
+ vec4_bool: { wgsl: 'alias T = vec4<bool>;', ok: true },
+ vec2_i32: { wgsl: 'alias T = vec2<i32>;', ok: true },
+ vec3_i32: { wgsl: 'alias T = vec3<i32>;', ok: true },
+ vec4_i32: { wgsl: 'alias T = vec4<i32>;', ok: true },
+ vec2_u32: { wgsl: 'alias T = vec2<u32>;', ok: true },
+ vec3_u32: { wgsl: 'alias T = vec3<u32>;', ok: true },
+ vec4_u32: { wgsl: 'alias T = vec4<u32>;', ok: true },
+ vec2_f32: { wgsl: 'alias T = vec2<f32>;', ok: true },
+ vec3_f32: { wgsl: 'alias T = vec3<f32>;', ok: true },
+ vec4_f32: { wgsl: 'alias T = vec4<f32>;', ok: true },
+ vec2_f16: { wgsl: 'enable f16;\nalias T = vec2<f16>;', ok: true },
+ vec3_f16: { wgsl: 'enable f16;\nalias T = vec3<f16>;', ok: true },
+ vec4_f16: { wgsl: 'enable f16;\nalias T = vec4<f16>;', ok: true },
+
+ // Pre-declared type aliases
+ vec2i: { wgsl: 'const c : vec2i = vec2<i32>();', ok: true },
+ vec3i: { wgsl: 'const c : vec3i = vec3<i32>();', ok: true },
+ vec4i: { wgsl: 'const c : vec4i = vec4<i32>();', ok: true },
+ vec2u: { wgsl: 'const c : vec2u = vec2<u32>();', ok: true },
+ vec3u: { wgsl: 'const c : vec3u = vec3<u32>();', ok: true },
+ vec4u: { wgsl: 'const c : vec4u = vec4<u32>();', ok: true },
+ vec2f: { wgsl: 'const c : vec2f = vec2<f32>();', ok: true },
+ vec3f: { wgsl: 'const c : vec3f = vec3<f32>();', ok: true },
+ vec4f: { wgsl: 'const c : vec4f = vec4<f32>();', ok: true },
+ vec2h: { wgsl: 'enable f16;\nconst c : vec2h = vec2<f16>();', ok: true },
+ vec3h: { wgsl: 'enable f16;\nconst c : vec3h = vec3<f16>();', ok: true },
+ vec4h: { wgsl: 'enable f16;\nconst c : vec4h = vec4<f16>();', ok: true },
+
+ // pass
+ trailing_comma: { wgsl: 'alias T = vec3<u32,>;', ok: true },
+ aliased_el_ty: { wgsl: 'alias EL = i32;\nalias T = vec3<EL>;', ok: true },
+
+ // invalid
+ vec: { wgsl: 'alias T = vec;', ok: false },
+ vec_f32: { wgsl: 'alias T = vec<f32>;', ok: false },
+ vec1_i32: { wgsl: 'alias T = vec1<i32>;', ok: false },
+ vec5_u32: { wgsl: 'alias T = vec5<u32>;', ok: false },
+ missing_el_ty: { wgsl: 'alias T = vec3<>;', ok: false },
+ missing_t_left: { wgsl: 'alias T = vec3 u32>;', ok: false },
+ missing_t_right: { wgsl: 'alias T = vec3<u32;', ok: false },
+ vec_of_array: { wgsl: 'alias T = vec3<array<i32, 2>>;', ok: false },
+ vec_of_runtime_array: { wgsl: 'alias T = vec3<array<i32>>;', ok: false },
+ vec_of_struct: { wgsl: 'struct S { i : i32 }\nalias T = vec3<S>;', ok: false },
+ vec_of_atomic: { wgsl: 'alias T = vec3<atomic<i32>>;', ok: false },
+ vec_of_matrix: { wgsl: 'alias T = vec3<mat2x2f>;', ok: false },
+ vec_of_vec: { wgsl: 'alias T = vec3<vec2f>;', ok: false },
+ no_bool_shortform: { wgsl: 'const c : vec2b = vec2<bool>();', ok: false }
+};
+
+g.test('vector').
+desc('Tests validation of vector types').
+params(
+ (u) => u.combine('case', keysOf(kCases)) //
+).
+beforeAllSubcases((t) => {
+ const c = kCases[t.params.case];
+ if (c.wgsl.indexOf('enable f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+}).
+fn((t) => {
+ const c = kCases[t.params.case];
+ t.expectCompileResult(c.ok, c.wgsl);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/uniformity/uniformity.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/uniformity/uniformity.spec.js
new file mode 100644
index 0000000000..a656f731f5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/validation/uniformity/uniformity.spec.js
@@ -0,0 +1,2444 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Validation tests for uniformity analysis`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { unreachable } from '../../../../common/util/util.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kCollectiveOps = [
+{ op: 'textureSample', stage: 'fragment' },
+{ op: 'textureSampleBias', stage: 'fragment' },
+{ op: 'textureSampleCompare', stage: 'fragment' },
+{ op: 'dpdx', stage: 'fragment' },
+{ op: 'dpdxCoarse', stage: 'fragment' },
+{ op: 'dpdxFine', stage: 'fragment' },
+{ op: 'dpdy', stage: 'fragment' },
+{ op: 'dpdyCoarse', stage: 'fragment' },
+{ op: 'dpdyFine', stage: 'fragment' },
+{ op: 'fwidth', stage: 'fragment' },
+{ op: 'fwidthCoarse', stage: 'fragment' },
+{ op: 'fwidthFine', stage: 'fragment' },
+{ op: 'storageBarrier', stage: 'compute' },
+{ op: 'workgroupBarrier', stage: 'compute' },
+{ op: 'workgroupUniformLoad', stage: 'compute' }];
+
+
+const kConditions = [
+{ cond: 'uniform_storage_ro', expectation: true },
+{ cond: 'nonuniform_storage_ro', expectation: false },
+{ cond: 'nonuniform_storage_rw', expectation: false },
+{ cond: 'nonuniform_builtin', expectation: false },
+{ cond: 'uniform_literal', expectation: true },
+{ cond: 'uniform_const', expectation: true },
+{ cond: 'uniform_override', expectation: true },
+{ cond: 'uniform_let', expectation: true },
+{ cond: 'nonuniform_let', expectation: false },
+{ cond: 'uniform_or', expectation: true },
+{ cond: 'nonuniform_or1', expectation: false },
+{ cond: 'nonuniform_or2', expectation: false },
+{ cond: 'uniform_and', expectation: true },
+{ cond: 'nonuniform_and1', expectation: false },
+{ cond: 'nonuniform_and2', expectation: false },
+{ cond: 'uniform_func_var', expectation: true },
+{ cond: 'nonuniform_func_var', expectation: false }];
+
+
+function generateCondition(condition) {
+ switch (condition) {
+ case 'uniform_storage_ro':{
+ return `ro_buffer[0] == 0`;
+ }
+ case 'nonuniform_storage_ro':{
+ return `ro_buffer[priv_var[0]] == 0`;
+ }
+ case 'nonuniform_storage_rw':{
+ return `rw_buffer[0] == 0`;
+ }
+ case 'nonuniform_builtin':{
+ return `p.x == 0`;
+ }
+ case 'uniform_literal':{
+ return `false`;
+ }
+ case 'uniform_const':{
+ return `c`;
+ }
+ case 'uniform_override':{
+ return `o == 0`;
+ }
+ case 'uniform_let':{
+ return `u_let == 0`;
+ }
+ case 'nonuniform_let':{
+ return `n_let == 0`;
+ }
+ case 'uniform_or':{
+ return `u_let == 0 || uniform_buffer.y > 1`;
+ }
+ case 'nonuniform_or1':{
+ return `u_let == 0 || n_let == 0`;
+ }
+ case 'nonuniform_or2':{
+ return `n_let == 0 || u_let == 0`;
+ }
+ case 'uniform_and':{
+ return `u_let == 0 && uniform_buffer.y > 1`;
+ }
+ case 'nonuniform_and1':{
+ return `u_let == 0 && n_let == 0`;
+ }
+ case 'nonuniform_and2':{
+ return `n_let == 0 && u_let == 0`;
+ }
+ case 'uniform_func_var':{
+ return `u_f == 0`;
+ }
+ case 'nonuniform_func_var':{
+ return `n_f == 0`;
+ }
+ default:{
+ unreachable(`Unhandled condition`);
+ }
+ }
+}
+
+function generateOp(op) {
+ switch (op) {
+ case 'textureSample':{
+ return `let x = ${op}(tex, s, vec2(0,0));\n`;
+ }
+ case 'textureSampleBias':{
+ return `let x = ${op}(tex, s, vec2(0,0), 0);\n`;
+ }
+ case 'textureSampleCompare':{
+ return `let x = ${op}(tex_depth, s_comp, vec2(0,0), 0);\n`;
+ }
+ case 'storageBarrier':
+ case 'workgroupBarrier':{
+ return `${op}();\n`;
+ }
+ case 'workgroupUniformLoad':{
+ return `let x = ${op}(&wg);`;
+ }
+ case 'dpdx':
+ case 'dpdxCoarse':
+ case 'dpdxFine':
+ case 'dpdy':
+ case 'dpdyCoarse':
+ case 'dpdyFine':
+ case 'fwidth':
+ case 'fwidthCoarse':
+ case 'fwidthFine':{
+ return `let x = ${op}(0);\n`;
+ }
+ default:{
+ unreachable(`Unhandled op`);
+ }
+ }
+}
+
+function generateConditionalStatement(statement, condition, op) {
+ const code = ``;
+ switch (statement) {
+ case 'if':{
+ return `if ${generateCondition(condition)} {
+ ${generateOp(op)};
+ }
+ `;
+ }
+ case 'for':{
+ return `for (; ${generateCondition(condition)};) {
+ ${generateOp(op)};
+ }
+ `;
+ }
+ case 'while':{
+ return `while ${generateCondition(condition)} {
+ ${generateOp(op)};
+ }
+ `;
+ }
+ case 'switch':{
+ return `switch u32(${generateCondition(condition)}) {
+ case 0: {
+ ${generateOp(op)};
+ }
+ default: { }
+ }
+ `;
+ }
+ default:{
+ unreachable(`Unhandled statement`);
+ }
+ }
+
+ return code;
+}
+
+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']).
+beginSubcases()
+).
+fn((t) => {
+ let code = `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var s_comp : sampler_comparison;
+ @group(0) @binding(2) var tex : texture_2d<f32>;
+ @group(0) @binding(3) var tex_depth : texture_depth_2d;
+
+ @group(1) @binding(0) var<storage, read> ro_buffer : array<f32, 4>;
+ @group(1) @binding(1) var<storage, read_write> rw_buffer : array<f32, 4>;
+ @group(1) @binding(2) var<uniform> uniform_buffer : vec4<f32>;
+
+ var<private> priv_var : array<f32, 4> = array(0,0,0,0);
+
+ const c = false;
+ override o : f32;
+`;
+
+ if (t.params.stage === 'compute') {
+ code += `var<workgroup> wg : f32;\n`;
+ code += ` @workgroup_size(16, 1, 1)`;
+ }
+ code += `@${t.params.stage}`;
+ code += `\nfn main(`;
+ if (t.params.stage === 'compute') {
+ code += `@builtin(global_invocation_id) p : vec3<u32>`;
+ } else {
+ code += `@builtin(position) p : vec4<f32>`;
+ }
+ code += `) {
+ let u_let = uniform_buffer.x;
+ let n_let = rw_buffer[0];
+ var u_f = uniform_buffer.z;
+ var n_f = rw_buffer[1];
+ `;
+
+ // Simple control statement containing the op.
+ code += generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
+
+ code += `\n}\n`;
+
+ t.expectCompileResult(t.params.expectation, code);
+});
+
+const kFragmentBuiltinValues = [
+{
+ builtin: `position`,
+ type: `vec4<f32>`
+},
+{
+ builtin: `front_facing`,
+ type: `bool`
+},
+{
+ builtin: `sample_index`,
+ type: `u32`
+},
+{
+ builtin: `sample_mask`,
+ type: `u32`
+}];
+
+
+g.test('fragment_builtin_values').
+desc(`Test uniformity of fragment built-in values`).
+params((u) => u.combineWithParams(kFragmentBuiltinValues).beginSubcases()).
+fn((t) => {
+ let cond = ``;
+ switch (t.params.type) {
+ case `u32`:
+ case `i32`:
+ case `f32`:{
+ cond = `p > 0`;
+ break;
+ }
+ case `vec4<u32>`:
+ case `vec4<i32>`:
+ case `vec4<f32>`:{
+ cond = `p.x > 0`;
+ break;
+ }
+ case `bool`:{
+ cond = `p`;
+ break;
+ }
+ default:{
+ unreachable(`Unhandled type`);
+ }
+ }
+ const code = `
+@group(0) @binding(0) var s : sampler;
+@group(0) @binding(1) var tex : texture_2d<f32>;
+
+@fragment
+fn main(@builtin(${t.params.builtin}) p : ${t.params.type}) {
+ if ${cond} {
+ let texel = textureSample(tex, s, vec2<f32>(0,0));
+ }
+}
+`;
+
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ t.expectCompileResult(false, code);
+});
+
+const kComputeBuiltinValues = [
+{
+ builtin: `local_invocation_id`,
+ type: `vec3<f32>`,
+ uniform: false
+},
+{
+ builtin: `local_invocation_index`,
+ type: `u32`,
+ uniform: false
+},
+{
+ builtin: `global_invocation_id`,
+ type: `vec3<u32>`,
+ uniform: false
+},
+{
+ builtin: `workgroup_id`,
+ type: `vec3<u32>`,
+ uniform: true
+},
+{
+ builtin: `num_workgroups`,
+ type: `vec3<u32>`,
+ uniform: true
+}];
+
+
+g.test('compute_builtin_values').
+desc(`Test uniformity of compute built-in values`).
+params((u) => u.combineWithParams(kComputeBuiltinValues).beginSubcases()).
+fn((t) => {
+ let cond = ``;
+ switch (t.params.type) {
+ case `u32`:
+ case `i32`:
+ case `f32`:{
+ cond = `p > 0`;
+ break;
+ }
+ case `vec3<u32>`:
+ case `vec3<i32>`:
+ case `vec3<f32>`:{
+ cond = `p.x > 0`;
+ break;
+ }
+ case `bool`:{
+ cond = `p`;
+ break;
+ }
+ default:{
+ unreachable(`Unhandled type`);
+ }
+ }
+ const code = `
+@compute @workgroup_size(16,1,1)
+fn main(@builtin(${t.params.builtin}) p : ${t.params.type}) {
+ if ${cond} {
+ workgroupBarrier();
+ }
+}
+`;
+
+ t.expectCompileResult(t.params.uniform, code);
+});
+
+function generatePointerCheck(check) {
+ if (check === `address`) {
+ return `let tmp = workgroupUniformLoad(ptr);`;
+ } else {
+ // check === `contents`
+ return `if test_val > 0 {
+ workgroupBarrier();
+ }`;
+ }
+}
+
+const kPointerCases = {
+ address_uniform_literal: {
+ code: `let ptr = &wg_array[0];`,
+ check: `address`,
+ uniform: true
+ },
+ address_uniform_value: {
+ code: `let ptr = &wg_array[uniform_value];`,
+ check: `address`,
+ uniform: true
+ },
+ address_nonuniform_value: {
+ code: `let ptr = &wg_array[nonuniform_value];`,
+ check: `address`,
+ uniform: false
+ },
+ address_uniform_chain: {
+ code: `let p1 = &wg_struct.x;
+ let p2 = &(*p1)[uniform_value];
+ let p3 = &(*p2).x;
+ let ptr = &(*p3)[uniform_value];`,
+ check: `address`,
+ uniform: true
+ },
+ address_nonuniform_chain1: {
+ code: `let p1 = &wg_struct.x;
+ let p2 = &(*p1)[nonuniform_value];
+ let p3 = &(*p2).x;
+ let ptr = &(*p3)[uniform_value];`,
+ check: `address`,
+ uniform: false
+ },
+ address_nonuniform_chain2: {
+ code: `let p1 = &wg_struct.x;
+ let p2 = &(*p1)[uniform_value];
+ let p3 = &(*p2).x;
+ let ptr = &(*p3)[nonuniform_value];`,
+ check: `address`,
+ uniform: false
+ },
+ wg_uniform_load_is_uniform: {
+ code: `let test_val = workgroupUniformLoad(&wg_scalar);`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_scalar_uniform1: {
+ code: `let ptr = &func_scalar;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_scalar_uniform2: {
+ code: `func_scalar = nonuniform_value;
+ let ptr = &func_scalar;
+ func_scalar = 0;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_scalar_uniform3: {
+ code: `let ptr = &func_scalar;
+ func_scalar = nonuniform_value;
+ func_scalar = uniform_value;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_scalar_nonuniform1: {
+ code: `func_scalar = nonuniform_value;
+ let ptr = &func_scalar;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_scalar_nonuniform2: {
+ code: `let ptr = &func_scalar;
+ *ptr = nonuniform_value;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_scalar_alias_uniform: {
+ code: `let p = &func_scalar;
+ let ptr = p;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_scalar_alias_nonuniform1: {
+ code: `func_scalar = nonuniform_value;
+ let p = &func_scalar;
+ let ptr = p;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_scalar_alias_nonuniform2: {
+ code: `let p = &func_scalar;
+ *p = nonuniform_value;
+ let ptr = p;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_scalar_alias_nonuniform3: {
+ code: `let p = &func_scalar;
+ let ptr = p;
+ *p = nonuniform_value;
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_scalar_alias_nonuniform4: {
+ code: `let p = &func_scalar;
+ func_scalar = nonuniform_value;
+ let test_val = *p;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_scalar_alias_nonuniform5: {
+ code: `let p = &func_scalar;
+ *p = nonuniform_value;
+ let test_val = func_scalar;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_array_uniform_index: {
+ code: `let ptr = &func_array[uniform_value];
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_array_nonuniform_index1: {
+ code: `let ptr = &func_array[nonuniform_value];
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_array_nonuniform_index2: {
+ code: `let ptr = &func_array[lid.x];
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_array_nonuniform_index3: {
+ code: `let ptr = &func_array[gid.x];
+ let test_val = *ptr;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_struct_uniform: {
+ code: `let p1 = &func_struct.x[uniform_value].x[uniform_value].x[uniform_value];
+ let test_val = *p1;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_struct_nonuniform1: {
+ code: `let p1 = &func_struct.x[nonuniform_value].x[uniform_value].x[uniform_value];
+ let test_val = *p1;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_struct_nonuniform2: {
+ code: `let p1 = &func_struct.x[uniform_value].x[gid.x].x[uniform_value];
+ let test_val = *p1;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_struct_nonuniform3: {
+ code: `let p1 = &func_struct.x[uniform_value].x[uniform_value].x[lid.y];
+ let test_val = *p1;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_struct_chain_uniform: {
+ code: `let p1 = &func_struct.x;
+ let p2 = &(*p1)[uniform_value];
+ let p3 = &(*p2).x;
+ let p4 = &(*p3)[uniform_value];
+ let p5 = &(*p4).x;
+ let p6 = &(*p5)[uniform_value];
+ let test_val = *p6;`,
+ check: `contents`,
+ uniform: true
+ },
+ contents_struct_chain_nonuniform1: {
+ code: `let p1 = &func_struct.x;
+ let p2 = &(*p1)[nonuniform_value];
+ let p3 = &(*p2).x;
+ let p4 = &(*p3)[uniform_value];
+ let p5 = &(*p4).x;
+ let p6 = &(*p5)[uniform_value];
+ let test_val = *p6;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_struct_chain_nonuniform2: {
+ code: `let p1 = &func_struct.x;
+ let p2 = &(*p1)[uniform_value];
+ let p3 = &(*p2).x;
+ let p4 = &(*p3)[gid.x];
+ let p5 = &(*p4).x;
+ let p6 = &(*p5)[uniform_value];
+ let test_val = *p6;`,
+ check: `contents`,
+ uniform: false
+ },
+ contents_struct_chain_nonuniform3: {
+ code: `let p1 = &func_struct.x;
+ let p2 = &(*p1)[uniform_value];
+ let p3 = &(*p2).x;
+ let p4 = &(*p3)[uniform_value];
+ let p5 = &(*p4).x;
+ let p6 = &(*p5)[lid.y];
+ let test_val = *p6;`,
+ check: `contents`,
+ uniform: false
+ }
+};
+
+g.test('pointers').
+desc(`Test pointer uniformity (contents and addresses)`).
+params((u) => u.combine('case', keysOf(kPointerCases)).beginSubcases()).
+fn((t) => {
+ const testcase = kPointerCases[t.params.case];
+ const code = `
+var<workgroup> wg_scalar : u32;
+var<workgroup> wg_array : array<u32, 16>;
+
+struct Inner {
+ x : array<u32, 4>
+}
+struct Middle {
+ x : array<Inner, 4>
+}
+struct Outer {
+ x : array<Middle, 4>
+}
+var<workgroup> wg_struct : Outer;
+
+@group(0) @binding(0)
+var<storage> uniform_value : u32;
+@group(0) @binding(1)
+var<storage, read_write> nonuniform_value : u32;
+
+@compute @workgroup_size(16, 1, 1)
+fn main(@builtin(local_invocation_id) lid : vec3<u32>,
+ @builtin(global_invocation_id) gid : vec3<u32>) {
+ var func_scalar : u32;
+ var func_array : array<u32, 16>;
+ var func_struct : Outer;
+
+ ${testcase.code}
+`;
+
+ const with_check =
+ code +
+ `
+${generatePointerCheck(testcase.check)}
+}`;
+ if (!testcase.uniform) {
+ const without_check = code + `}\n`;
+ t.expectCompileResult(true, without_check);
+ }
+ t.expectCompileResult(testcase.uniform, with_check);
+});
+
+function expectedUniformity(uniform, init) {
+ if (uniform === `always`) {
+ return true;
+ } else if (uniform === `init`) {
+ return init === `no_init` || init === `uniform`;
+ }
+
+ // uniform == `never` (or unknown values)
+ return false;
+}
+
+const kFuncVarCases = {
+ no_assign: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: ``,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ simple_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `x = uniform_value[0];`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ simple_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `x = nonuniform_value[0];`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ compound_assign_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `x += uniform_value[0];`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ compound_assign_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `x -= nonuniform_value[0];`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ unreachable_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ break;
+ x = uniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ unreachable_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ break;
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ if_no_else_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = uniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ if_no_else_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ if_no_then_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ } else {
+ x = uniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ if_no_then_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ } else {
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ if_else_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = uniform_value[0];
+ } else {
+ x = uniform_value[1];
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ if_else_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = nonuniform_value[0];
+ } else {
+ x = nonuniform_value[1];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ if_else_split: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = uniform_value[0];
+ } else {
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ if_unreachable_else_none: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ } else {
+ return;
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ if_unreachable_else_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = uniform_value[0];
+ } else {
+ return;
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ if_unreachable_else_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = nonuniform_value[0];
+ } else {
+ return;
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ if_unreachable_then_none: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ return;
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ if_unreachable_then_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ return;
+ } else {
+ x = uniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ if_unreachable_then_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ return;
+ } else {
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ if_nonescaping_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `if uniform_cond {
+ x = nonuniform_value[0];
+ return;
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ loop_body_depends_on_continuing_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ continuing {
+ x = uniform_value[0];
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `init`
+ },
+ loop_body_depends_on_continuing_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ continuing {
+ x = nonuniform_value[0];
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `never`
+ },
+ loop_body_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ x = uniform_value[0];
+ continuing {
+ break if uniform_cond;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ loop_body_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ x = nonuniform_value[0];
+ continuing {
+ break if uniform_cond;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ loop_body_nonuniform_cond: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ // The analysis doesn't recognize the content of the value.
+ x = uniform_value[0];
+ continuing {
+ break if nonuniform_cond;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ loop_unreachable_continuing: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ break;
+ continuing {
+ break if uniform_cond;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ loop_continuing_from_body_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ x = uniform_value[0];
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `always`
+ },
+ loop_continuing_from_body_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ x = nonuniform_value[0];
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `never`
+ },
+ loop_continuing_from_body_split1: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if uniform_cond {
+ x = uniform_value[0];
+ }
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `init`
+ },
+ loop_continuing_from_body_split2: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if uniform_cond {
+ x = nonuniform_value[0];
+ }
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `never`
+ },
+ loop_continuing_from_body_split3: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if uniform_cond {
+ x = uniform_value[0];
+ } else {
+ x = uniform_value[1];
+ }
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `always`
+ },
+ loop_continuing_from_body_split4: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if nonuniform_cond {
+ x = uniform_value[0];
+ } else {
+ x = uniform_value[1];
+ }
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `never`
+ },
+ loop_continuing_from_body_split5: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if nonuniform_cond {
+ x = uniform_value[0];
+ } else {
+ x = uniform_value[0];
+ }
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ // The analysis doesn't recognize that uniform_value[0] is assignment on all paths.
+ uniform: `never`
+ },
+ loop_in_loop_with_continue_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ loop {
+ x = nonuniform_value[0];
+ if nonuniform_cond {
+ break;
+ }
+ continue;
+ }
+ x = uniform_value[0];
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `always`
+ },
+ loop_in_loop_with_continue_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ loop {
+ x = uniform_value[0];
+ if uniform_cond {
+ break;
+ }
+ continue;
+ }
+ x = nonuniform_value[0];
+ continuing {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ break if uniform_cond;
+ }
+ }`,
+ cond: `true`, // override the standard check
+ uniform: `never`
+ },
+ after_loop_with_uniform_break_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if uniform_cond {
+ x = uniform_value[0];
+ break;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ after_loop_with_uniform_break_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if uniform_cond {
+ x = nonuniform_value[0];
+ break;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ after_loop_with_nonuniform_break: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if nonuniform_cond {
+ x = uniform_value[0];
+ break;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ after_loop_with_uniform_breaks: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `loop {
+ if uniform_cond {
+ x = uniform_value[0];
+ break;
+ } else {
+ break;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ switch_uniform_case: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch uniform_val {
+ case 0 {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ }
+ default {
+ }
+ }`,
+ cond: `true`, // override default check
+ uniform: `init`
+ },
+ switch_nonuniform_case: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch nonuniform_val {
+ case 0 {
+ if x > 0 {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ }
+ default {
+ }
+ }`,
+ cond: `true`, // override default check
+ uniform: `never`
+ },
+ after_switch_all_uniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch uniform_val {
+ case 0 {
+ x = uniform_value[0];
+ }
+ case 1,2 {
+ x = uniform_value[1];
+ }
+ default {
+ x = uniform_value[2];
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ after_switch_some_assign: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch uniform_val {
+ case 0 {
+ x = uniform_value[0];
+ }
+ case 1,2 {
+ x = uniform_value[1];
+ }
+ default {
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ after_switch_nonuniform: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch uniform_val {
+ case 0 {
+ x = uniform_value[0];
+ }
+ case 1,2 {
+ x = uniform_value[1];
+ }
+ default {
+ x = nonuniform_value[0];
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ after_switch_with_break_nonuniform1: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch uniform_val {
+ default {
+ if uniform_cond {
+ x = uniform_value[0];
+ break;
+ }
+ x = nonuniform_value[0];
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ after_switch_with_break_nonuniform2: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `switch uniform_val {
+ default {
+ x = uniform_value[0];
+ if uniform_cond {
+ x = nonuniform_value[0];
+ break;
+ }
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ for_loop_uniform_body: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (var i = 0; i < 10; i += 1) {
+ x = uniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ for_loop_nonuniform_body: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (var i = 0; i < 10; i += 1) {
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ for_loop_uniform_body_no_condition: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (var i = 0; ; i += 1) {
+ x = uniform_value[0];
+ if uniform_cond {
+ break;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ for_loop_nonuniform_body_no_condition: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (var i = 0; ; i += 1) {
+ x = nonuniform_value[0];
+ if uniform_cond {
+ break;
+ }
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ for_loop_uniform_increment: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (; uniform_cond; x += uniform_value[0]) {
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ for_loop_nonuniform_increment: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (; uniform_cond; x += nonuniform_value[0]) {
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ for_loop_uniform_init: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (x = uniform_value[0]; uniform_cond; ) {
+ }`,
+ cond: `x > 0`,
+ uniform: `always`
+ },
+ for_loop_nonuniform_init: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `for (x = nonuniform_value[0]; uniform_cond;) {
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ while_loop_uniform_body: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `while uniform_cond {
+ x = uniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `init`
+ },
+ while_loop_nonuniform_body: {
+ typename: `u32`,
+ typedecl: ``,
+ assignment: `while uniform_cond {
+ x = nonuniform_value[0];
+ }`,
+ cond: `x > 0`,
+ uniform: `never`
+ },
+ partial_assignment_uniform: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32,
+ y : u32
+ }`,
+ assignment: `x.x = uniform_value[0].x;`,
+ cond: `x.x > 0`,
+ uniform: `init`
+ },
+ partial_assignment_nonuniform: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32,
+ y : u32
+ }`,
+ assignment: `x.x = nonuniform_value[0].x;`,
+ cond: `x.x > 0`,
+ uniform: `never`
+ },
+ partial_assignment_all_members_uniform: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32,
+ y : u32
+ }`,
+ assignment: `x.x = uniform_value[0].x;
+ x.y = uniform_value[1].y;`,
+ cond: `x.x > 0`,
+ uniform: `init`
+ },
+ partial_assignment_all_members_nonuniform: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32,
+ y : u32
+ }`,
+ assignment: `x.x = nonuniform_value[0].x;
+ x.y = uniform_value[0].x;`,
+ cond: `x.x > 0`,
+ uniform: `never`
+ },
+ partial_assignment_single_element_struct_uniform: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32
+ }`,
+ assignment: `x.x = uniform_value[0].x;`,
+ cond: `x.x > 0`,
+ uniform: `init`
+ },
+ partial_assignment_single_element_struct_nonuniform: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32
+ }`,
+ assignment: `x.x = nonuniform_value[0].x;`,
+ cond: `x.x > 0`,
+ uniform: `never`
+ },
+ partial_assignment_single_element_array_uniform: {
+ typename: `array<u32, 1>`,
+ typedecl: ``,
+ assignment: `x[0] = uniform_value[0][0];`,
+ cond: `x[0] > 0`,
+ uniform: `init`
+ },
+ partial_assignment_single_element_array_nonuniform: {
+ typename: `array<u32, 1>`,
+ typedecl: ``,
+ assignment: `x[0] = nonuniform_value[0][0];`,
+ cond: `x[0] > 0`,
+ uniform: `never`
+ },
+ nested1: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32,
+ y : u32
+ }`,
+ assignment: `for (; uniform_cond; ) {
+ if uniform_cond {
+ x = uniform_value[0];
+ break;
+ x.y = nonuniform_value[0].y;
+ } else {
+ if uniform_cond {
+ continue;
+ }
+ x = uniform_value[1];
+ }
+ }`,
+ cond: `x.x > 0`,
+ uniform: `init`
+ },
+ nested2: {
+ typename: `block`,
+ typedecl: `struct block {
+ x : u32,
+ y : u32
+ }`,
+ assignment: `for (; uniform_cond; ) {
+ if uniform_cond {
+ x = uniform_value[0];
+ break;
+ x.y = nonuniform_value[0].y;
+ } else {
+ if nonuniform_cond {
+ continue;
+ }
+ x = uniform_value[1];
+ }
+ }`,
+ cond: `x.x > 0`,
+ uniform: `never`
+ }
+};
+
+const kVarInit = {
+ no_init: ``,
+ uniform: `= uniform_value[3];`,
+ nonuniform: `= nonuniform_value[3];`
+};
+
+g.test('function_variables').
+desc(`Test uniformity of function variables`).
+params((u) => u.combine('case', keysOf(kFuncVarCases)).combine('init', keysOf(kVarInit))).
+fn((t) => {
+ const func_case = kFuncVarCases[t.params.case];
+ const code = `
+${func_case.typedecl}
+
+@group(0) @binding(0)
+var<storage> uniform_value : array<${func_case.typename}, 4>;
+@group(0) @binding(1)
+var<storage, read_write> nonuniform_value : array<${func_case.typename}, 4>;
+
+@group(1) @binding(0)
+var t : texture_2d<f32>;
+@group(1) @binding(1)
+var s : sampler;
+
+var<private> nonuniform_cond : bool = true;
+const uniform_cond : bool = true;
+var<private> nonuniform_val : u32 = 0;
+const uniform_val : u32 = 0;
+
+@fragment
+fn main() {
+ var x : ${func_case.typename} ${kVarInit[t.params.init]};
+
+ ${func_case.assignment}
+
+ if ${func_case.cond} {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+}
+`;
+
+ const result = expectedUniformity(func_case.uniform, t.params.init);
+ if (!result) {
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ }
+ t.expectCompileResult(result, code);
+});
+
+const kShortCircuitExpressionCases = {
+ or_uniform_uniform: {
+ code: `
+ let x = uniform_cond || uniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: true
+ },
+ or_uniform_nonuniform: {
+ code: `
+ let x = uniform_cond || nonuniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: false
+ },
+ or_nonuniform_uniform: {
+ code: `
+ let x = nonuniform_cond || uniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: false
+ },
+ or_nonuniform_nonuniform: {
+ code: `
+ let x = nonuniform_cond || nonuniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: false
+ },
+ or_uniform_first_nonuniform: {
+ code: `
+ let x = textureSample(t, s, vec2f(0,0)).x == 0 || nonuniform_cond;
+ `,
+ uniform: true
+ },
+ or_uniform_second_nonuniform: {
+ code: `
+ let x = nonuniform_cond || textureSample(t, s, vec2f(0,0)).x == 0;
+ `,
+ uniform: false
+ },
+ and_uniform_uniform: {
+ code: `
+ let x = uniform_cond && uniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: true
+ },
+ and_uniform_nonuniform: {
+ code: `
+ let x = uniform_cond && nonuniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: false
+ },
+ and_nonuniform_uniform: {
+ code: `
+ let x = nonuniform_cond && uniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: false
+ },
+ and_nonuniform_nonuniform: {
+ code: `
+ let x = nonuniform_cond && nonuniform_cond;
+ if x {
+ let tmp = textureSample(t, s, vec2f(0,0));
+ }
+ `,
+ uniform: false
+ },
+ and_uniform_first_nonuniform: {
+ code: `
+ let x = textureSample(t, s, vec2f(0,0)).x == 0 && nonuniform_cond;
+ `,
+ uniform: true
+ },
+ and_uniform_second_nonuniform: {
+ code: `
+ let x = nonuniform_cond && textureSample(t, s, vec2f(0,0)).x == 0;
+ `,
+ uniform: false
+ }
+};
+
+const kPointerParamCases = {
+ pointer_uniform_passthrough_value: {
+ function: `fn foo(p : ptr<function, u32>) -> u32 {
+ return *p;
+ }`,
+ call: `var x = uniform_values[0];
+ let call = foo(&x);`,
+ cond: `x > 0`,
+ uniform: true
+ },
+ pointer_nonuniform_passthrough_value: {
+ function: `fn foo(p : ptr<function, u32>) -> u32 {
+ return *p;
+ }`,
+ call: `var x = uniform_values[0];
+ let call = foo(&x);`,
+ cond: `x > 0`,
+ uniform: true
+ },
+ pointer_store_uniform_value: {
+ function: `fn foo(p : ptr<function, u32>) {
+ *p = uniform_values[0];
+ }`,
+ call: `var x = nonuniform_values[0];
+ foo(&x);`,
+ cond: `x > 0`,
+ uniform: true
+ },
+ pointer_store_nonuniform_value: {
+ function: `fn foo(p : ptr<function, u32>) {
+ *p = nonuniform_values[0];
+ }`,
+ call: `var x = uniform_values[0];
+ foo(&x);`,
+ cond: `x > 0`,
+ uniform: false
+ },
+ pointer_depends_on_nonpointer_param_uniform: {
+ function: `fn foo(p : ptr<function, u32>, x : u32) {
+ *p = x;
+ }`,
+ call: `var x = nonuniform_values[0];
+ foo(&x, uniform_values[0]);`,
+ cond: `x > 0`,
+ uniform: true
+ },
+ pointer_depends_on_nonpointer_param_nonuniform: {
+ function: `fn foo(p : ptr<function, u32>, x : u32) {
+ *p = x;
+ }`,
+ call: `var x = uniform_values[0];
+ foo(&x, nonuniform_values[0]);`,
+ cond: `x > 0`,
+ uniform: false
+ },
+ pointer_depends_on_pointer_param_uniform: {
+ function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
+ *p = *q;
+ }`,
+ call: `var x = nonuniform_values[0];
+ var y = uniform_values[0];
+ foo(&x, &y);`,
+ cond: `x > 0`,
+ uniform: true
+ },
+ pointer_depends_on_pointer_param_nonuniform: {
+ function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
+ *p = *q;
+ }`,
+ call: `var x = uniform_values[0];
+ var y = nonuniform_values[0];
+ foo(&x, &y);`,
+ cond: `x > 0`,
+ uniform: false
+ },
+ pointer_codependent1: {
+ function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
+ if *p > 0 {
+ *p = *q;
+ } else {
+ *q++;
+ }
+ }`,
+ call: `var x = uniform_values[0];
+ var y = uniform_values[1];
+ foo(&x, &y);
+ let a = x + y;`,
+ cond: `a > 0`,
+ uniform: true
+ },
+ pointer_codependent2: {
+ function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
+ if *p > 0 {
+ *p = *q;
+ } else {
+ *q++;
+ }
+ }`,
+ call: `var x = uniform_values[0];
+ var y = nonuniform_values[1];
+ foo(&x, &y);
+ let a = x + y;`,
+ cond: `a > 0`,
+ uniform: false
+ },
+ pointer_codependent3: {
+ function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
+ if *p > 0 {
+ *p = *q;
+ } else {
+ *q++;
+ }
+ }`,
+ call: `var x = nonuniform_values[0];
+ var y = uniform_values[1];
+ foo(&x, &y);
+ let a = x + y;`,
+ cond: `a > 0`,
+ uniform: false
+ },
+ pointer_codependent4: {
+ function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
+ if *p > 0 {
+ *p = *q;
+ } else {
+ *q++;
+ }
+ }`,
+ call: `var x = nonuniform_values[0];
+ var y = nonuniform_values[1];
+ foo(&x, &y);
+ let a = x + y;`,
+ cond: `a > 0`,
+ uniform: false
+ },
+ uniform_param_uniform_assignment: {
+ function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
+ (*p)[idx] = uniform_values[0];
+ }`,
+ call: `var x = array(uniform_values[0], uniform_values[1]);
+ foo(&x, uniform_values[3]);`,
+ cond: `x[0] > 0`,
+ uniform: true
+ },
+ uniform_param_nonuniform_assignment: {
+ function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
+ (*p)[idx] = nonuniform_values[0];
+ }`,
+ call: `var x = array(uniform_values[0], uniform_values[1]);
+ foo(&x, uniform_values[3]);`,
+ cond: `x[0] > 0`,
+ uniform: false
+ },
+ nonuniform_param_uniform_assignment: {
+ function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
+ (*p)[idx] = uniform_values[0];
+ }`,
+ call: `var x = array(uniform_values[0], uniform_values[1]);
+ foo(&x, u32(clamp(pos.x, 0, 1)));`,
+ cond: `x[0] > 0`,
+ uniform: false
+ },
+ nonuniform_param_nonuniform_assignment: {
+ function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
+ (*p)[idx] = nonuniform_values[0];
+ }`,
+ call: `var x = array(uniform_values[0], uniform_values[1]);
+ foo(&x, u32(clamp(pos.x, 0, 1)));`,
+ cond: `x[0] > 0`,
+ uniform: false
+ },
+ required_uniform_success: {
+ function: `fn foo(p : ptr<function, u32>) {
+ if *p > 0 {
+ let tmp = textureSample(t,s,vec2f(0,0));
+ }
+ }`,
+ call: `var x = uniform_values[0];
+ foo(&x);`,
+ cond: `uniform_cond`,
+ uniform: true
+ },
+ required_uniform_failure: {
+ function: `fn foo(p : ptr<function, u32>) {
+ if *p > 0 {
+ let tmp = textureSample(t,s,vec2f(0,0));
+ }
+ }`,
+ call: `var x = nonuniform_values[0];
+ foo(&x);`,
+ cond: `uniform_cond`,
+ uniform: false
+ },
+ uniform_conditional_call_assign_uniform: {
+ function: `fn foo(p : ptr<function, u32>) {
+ *p = uniform_values[0];
+ }`,
+ call: `var x = uniform_values[1];
+ if uniform_cond {
+ foo(&x);
+ }`,
+ cond: `x > 0`,
+ uniform: true
+ },
+ uniform_conditional_call_assign_nonuniform1: {
+ function: `fn foo(p : ptr<function, u32>) {
+ *p = nonuniform_values[0];
+ }`,
+ call: `var x = uniform_values[1];
+ if uniform_cond {
+ foo(&x);
+ }`,
+ cond: `x > 0`,
+ uniform: false
+ },
+ uniform_conditional_call_assign_nonuniform2: {
+ function: `fn foo(p : ptr<function, u32>) {
+ *p = uniform_values[0];
+ }`,
+ call: `var x = nonuniform_values[1];
+ if uniform_cond {
+ foo(&x);
+ }`,
+ cond: `x > 0`,
+ uniform: false
+ },
+ nonuniform_conditional_call_assign_uniform: {
+ function: `fn foo(p : ptr<function, u32>) {
+ *p = uniform_values[0];
+ }`,
+ call: `var x = uniform_values[1];
+ if nonuniform_cond {
+ foo(&x);
+ }`,
+ cond: `x > 0`,
+ uniform: false
+ }
+};
+
+g.test('function_pointer_parameters').
+desc(`Test functions and calls with pointer parameters`).
+params((u) => u.combine('case', keysOf(kPointerParamCases))).
+fn((t) => {
+ const pointer_case = kPointerParamCases[t.params.case];
+ const code = `
+@group(0) @binding(0)
+var t : texture_2d<f32>;
+@group(0) @binding(1)
+var s : sampler;
+
+const uniform_cond = true;
+var<private> nonuniform_cond = true;
+
+@group(1) @binding(0)
+var<storage> uniform_values : array<u32, 4>;
+@group(1) @binding(1)
+var<storage, read_write> nonuniform_values : array<u32, 4>;
+
+${pointer_case.function}
+
+@fragment
+fn main(@builtin(position) pos : vec4f) {
+ ${pointer_case.call}
+
+ if ${pointer_case.cond} {
+ let tmp = textureSample(t,s,vec2f(0,0));
+ }
+}
+`;
+
+ const res = pointer_case.uniform;
+ if (!res) {
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ }
+ t.expectCompileResult(res, code);
+});
+
+g.test('short_circuit_expressions').
+desc(`Test uniformity of expressions`).
+params((u) => u.combine('case', keysOf(kShortCircuitExpressionCases))).
+fn((t) => {
+ const testcase = kShortCircuitExpressionCases[t.params.case];
+ const code = `
+@group(1) @binding(0)
+var t : texture_2d<f32>;
+@group(1) @binding(1)
+var s : sampler;
+
+const uniform_cond = true;
+var<private> nonuniform_cond = false;
+
+@fragment
+fn main() {
+ ${testcase.code}
+}
+`;
+
+ const res = testcase.uniform;
+ if (!res) {
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ }
+ t.expectCompileResult(res, code);
+});
+
+const kExpressionCases = {
+ literal: {
+ code: `1u`,
+ uniform: true
+ },
+ uniform: {
+ code: `uniform_val`,
+ uniform: true
+ },
+ nonuniform: {
+ code: `nonuniform_val`,
+ uniform: false
+ },
+ uniform_index: {
+ code: `uniform_value[uniform_val]`,
+ uniform: true
+ },
+ nonuniform_index1: {
+ code: `uniform_value[nonuniform_val]`,
+ uniform: false
+ },
+ nonuniform_index2: {
+ code: `nonuniform_value[uniform_val]`,
+ uniform: false
+ },
+ uniform_struct: {
+ code: `uniform_struct.x`,
+ uniform: true
+ },
+ nonuniform_struct: {
+ code: `nonuniform_struct.x`,
+ uniform: false
+ }
+};
+
+const kBinOps = {
+ plus: {
+ code: '+',
+ test: '> 0'
+ },
+ minus: {
+ code: '-',
+ test: '> 0'
+ },
+ times: {
+ code: '*',
+ test: '> 0'
+ },
+ div: {
+ code: '/',
+ test: '> 0'
+ },
+ rem: {
+ code: '%',
+ test: '> 0'
+ },
+ and: {
+ code: '&',
+ test: '> 0'
+ },
+ or: {
+ code: '|',
+ test: '> 0'
+ },
+ xor: {
+ code: '^',
+ test: '> 0'
+ },
+ shl: {
+ code: '<<',
+ test: '> 0'
+ },
+ shr: {
+ code: '>>',
+ test: '> 0'
+ },
+ less: {
+ code: '<',
+ test: ''
+ },
+ lessequal: {
+ code: '<=',
+ test: ''
+ },
+ greater: {
+ code: '>',
+ test: ''
+ },
+ greaterequal: {
+ code: '>=',
+ test: ''
+ },
+ equal: {
+ code: '==',
+ test: ''
+ },
+ notequal: {
+ code: '!=',
+ test: ''
+ }
+};
+
+g.test('binary_expressions').
+desc(`Test uniformity of binary expressions`).
+params((u) =>
+u.
+combine('e1', keysOf(kExpressionCases)).
+combine('e2', keysOf(kExpressionCases)).
+combine('op', keysOf(kBinOps))
+).
+fn((t) => {
+ const e1 = kExpressionCases[t.params.e1];
+ const e2 = kExpressionCases[t.params.e2];
+ const op = kBinOps[t.params.op];
+ const code = `
+@group(0) @binding(0)
+var t : texture_2d<f32>;
+@group(0) @binding(1)
+var s : sampler;
+
+struct S {
+ x : u32
+}
+
+const uniform_struct = S(1);
+var<private> nonuniform_struct = S(1);
+
+const uniform_value : array<u32, 2> = array(1,1);
+var<private> nonuniform_value : array<u32, 2> = array(1,1);
+
+const uniform_val : u32 = 1;
+var<private> nonuniform_val : u32 = 1;
+
+@fragment
+fn main() {
+ let tmp = ${e1.code} ${op.code} ${e2.code};
+ if tmp ${op.test} {
+ let res = textureSample(t, s, vec2f(0,0));
+ }
+}
+`;
+
+ const res = e1.uniform && e2.uniform;
+ if (!res) {
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ }
+ t.expectCompileResult(res, code);
+});
+
+g.test('unary_expressions').
+desc(`Test uniformity of uniary expressions`).
+params((u) =>
+u.
+combine('e', keysOf(kExpressionCases)).
+combine('op', ['!b_tmp', '~i_tmp > 0', '-i32(i_tmp) > 0'])
+).
+fn((t) => {
+ const e = kExpressionCases[t.params.e];
+ const code = `
+@group(0) @binding(0)
+var t : texture_2d<f32>;
+@group(0) @binding(1)
+var s : sampler;
+
+struct S {
+ x : i32
+}
+
+const uniform_struct = S(1);
+var<private> nonuniform_struct = S(1);
+
+const uniform_value : array<i32, 2> = array(1,1);
+var<private> nonuniform_value : array<i32, 2> = array(1,1);
+
+const uniform_val : i32 = 1;
+var<private> nonuniform_val : i32 = 1;
+
+@fragment
+fn main() {
+ let i_tmp = ${e.code};
+ let b_tmp = bool(i_tmp);
+ let tmp = ${t.params.op};
+ if tmp {
+ let res = textureSample(t, s, vec2f(0,0));
+ }
+}
+`;
+
+ const res = e.uniform;
+ if (!res) {
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ }
+ t.expectCompileResult(res, code);
+});
+
+const kFunctionCases = {
+ uniform_result: {
+ function: `fn foo() -> u32 {
+ return uniform_values[0];
+ }`,
+ call: `let call = foo();`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ nonuniform_result: {
+ function: `fn foo() -> u32 {
+ return nonuniform_values[0];
+ }`,
+ call: `let call = foo();`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ nonuniform_return_is_uniform_after_call: {
+ function: `fn foo() {
+ if nonuniform_values[0] > 0 {
+ return;
+ } else {
+ return;
+ }
+ }`,
+ call: `foo();`,
+ cond: `uniform_cond`,
+ uniform: true
+ },
+ uniform_passthrough_parameter: {
+ function: `fn foo(x : u32) -> u32 {
+ return x;
+ }`,
+ call: `let call = foo(uniform_values[0]);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ nonuniform_passthrough_parameter: {
+ function: `fn foo(x : u32) -> u32 {
+ return x;
+ }`,
+ call: `let call = foo(nonuniform_values[0]);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ combined_parameters1: {
+ function: `fn foo(x : u32, y : u32) -> u32 {
+ return x + y;
+ }`,
+ call: `let call = foo(uniform_values[0], uniform_values[1]);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ combined_parameters2: {
+ function: `fn foo(x : u32, y : u32) -> u32 {
+ return x + y;
+ }`,
+ call: `let call = foo(nonuniform_values[0], uniform_values[1]);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ combined_parameters3: {
+ function: `fn foo(x : u32, y : u32) -> u32 {
+ return x + y;
+ }`,
+ call: `let call = foo(uniform_values[0], nonuniform_values[1]);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ combined_parameters4: {
+ function: `fn foo(x : u32, y : u32) -> u32 {
+ return x + y;
+ }`,
+ call: `let call = foo(nonuniform_values[0], nonuniform_values[1]);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ uniform_parameter_cf_after_nonuniform_expr: {
+ function: `fn foo(x : bool, y : vec4f) -> f32 {
+ return select(0, y.x, x);
+ }`,
+ call: `let call = foo(nonuniform_cond || uniform_cond, textureSample(t,s,vec2f(0,0)));`,
+ cond: `uniform_cond`,
+ uniform: true
+ },
+ required_uniform_function_call_in_uniform_cf: {
+ function: `fn foo() -> vec4f {
+ return textureSample(t,s,vec2f(0,0));
+ }`,
+ call: `if uniform_cond {
+ let call = foo();
+ }`,
+ cond: `uniform_cond`,
+ uniform: true
+ },
+ required_uniform_function_call_in_nonuniform_cf: {
+ function: `fn foo() -> vec4f {
+ return textureSample(t,s,vec2f(0,0));
+ }`,
+ call: `if nonuniform_cond {
+ let call = foo();
+ }`,
+ cond: `uniform_cond`,
+ uniform: false
+ },
+ required_uniform_function_call_in_nonuniform_cf2: {
+ function: `@diagnostic(warning, derivative_uniformity)
+ fn foo() -> vec4f {
+ return textureSample(t,s,vec2f(0,0));
+ }`,
+ call: `if nonuniform_cond {
+ let call = foo();
+ let sample = textureSample(t,s,vec2f(0,0));
+ }`,
+ cond: `uniform_cond`,
+ uniform: false
+ },
+ required_uniform_function_call_depends_on_uniform_param: {
+ function: `fn foo(x : bool) -> vec4f {
+ if x {
+ return textureSample(t,s,vec2f(0,0));
+ }
+ return vec4f(0);
+ }`,
+ call: `let call = foo(uniform_cond);`,
+ cond: `uniform_cond`,
+ uniform: true
+ },
+ required_uniform_function_call_depends_on_nonuniform_param: {
+ function: `fn foo(x : bool) -> vec4f {
+ if x {
+ return textureSample(t,s,vec2f(0,0));
+ }
+ return vec4f(0);
+ }`,
+ call: `let call = foo(nonuniform_cond);`,
+ cond: `uniform_cond`,
+ uniform: false
+ },
+ dpdx_nonuniform_result: {
+ function: ``,
+ call: `let call = dpdx(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ dpdy_nonuniform_result: {
+ function: ``,
+ call: `let call = dpdy(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ dpdxCoarse_nonuniform_result: {
+ function: ``,
+ call: `let call = dpdxCoarse(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ dpdyCoarse_nonuniform_result: {
+ function: ``,
+ call: `let call = dpdyCoarse(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ dpdxFine_nonuniform_result: {
+ function: ``,
+ call: `let call = dpdxFine(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ dpdyFine_nonuniform_result: {
+ function: ``,
+ call: `let call = dpdyFine(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ fwidth_nonuniform_result: {
+ function: ``,
+ call: `let call = fwidth(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ fwidthCoarse_nonuniform_result: {
+ function: ``,
+ call: `let call = fwidthCoarse(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ fwidthFine_nonuniform_result: {
+ function: ``,
+ call: `let call = fwidthFine(1);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ textureSample_nonuniform_result: {
+ function: ``,
+ call: `let call = textureSample(t,s,vec2f(0,0));`,
+ cond: `call.x > 0`,
+ uniform: false
+ },
+ textureSampleBias_nonuniform_result: {
+ function: ``,
+ call: `let call = textureSampleBias(t,s,vec2f(0,0), 0);`,
+ cond: `call.x > 0`,
+ uniform: false
+ },
+ textureSampleCompare_nonuniform_result: {
+ function: ``,
+ call: `let call = textureSampleCompare(td,sd,vec2f(0,0), 0);`,
+ cond: `call > 0`,
+ uniform: false
+ },
+ textureDimensions_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureDimensions(t);`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ textureGather_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureGather(0,t,s,vec2f(0,0));`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ textureGatherCompare_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureGatherCompare(td,sd,vec2f(0,0), 0);`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ textureLoad_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureLoad(t,vec2u(0,0),0);`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ textureNumLayers_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureNumLayers(ta);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ textureNumLevels_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureNumLevels(t);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ textureNumSamples_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureNumSamples(ts);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ textureSampleLevel_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureSampleLevel(t,s,vec2f(0,0),0);`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ textureSampleGrad_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureSampleGrad(t,s,vec2f(0,0),vec2f(0,0),vec2f(0,0));`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ textureSampleCompareLevel_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureSampleCompareLevel(td,sd,vec2f(0,0), 0);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ textureSampleBaseClampToEdge_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = textureSampleBaseClampToEdge(t,s,vec2f(0,0));`,
+ cond: `call.x > 0`,
+ uniform: true
+ },
+ min_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = min(0,0);`,
+ cond: `call > 0`,
+ uniform: true
+ },
+ value_constructor_uniform_input_uniform_result: {
+ function: ``,
+ call: `let call = vec2u(0,0);`,
+ cond: `call.x > 0`,
+ uniform: true
+ }
+};
+
+g.test('functions').
+desc(`Test uniformity of function calls (non-pointer parameters)`).
+params((u) => u.combine('case', keysOf(kFunctionCases))).
+fn((t) => {
+ const func_case = kFunctionCases[t.params.case];
+ const code = `
+@group(0) @binding(0)
+var t : texture_2d<f32>;
+@group(0) @binding(1)
+var s : sampler;
+@group(0) @binding(2)
+var td : texture_depth_2d;
+@group(0) @binding(3)
+var sd : sampler_comparison;
+@group(0) @binding(4)
+var ta : texture_2d_array<f32>;
+@group(0) @binding(5)
+var ts : texture_multisampled_2d<f32>;
+
+const uniform_cond = true;
+var<private> nonuniform_cond = true;
+
+@group(1) @binding(0)
+var<storage> uniform_values : array<u32, 4>;
+@group(1) @binding(1)
+var<storage, read_write> nonuniform_values : array<u32, 4>;
+
+${func_case.function}
+
+@fragment
+fn main() {
+ ${func_case.call}
+
+ if ${func_case.cond} {
+ let tmp = textureSample(t,s,vec2f(0,0));
+ }
+}
+`;
+
+ const res = func_case.uniform;
+ if (!res) {
+ t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
+ }
+ t.expectCompileResult(res, code);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/values.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/values.js
new file mode 100644
index 0000000000..4c75635ea9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/shader/values.js
@@ -0,0 +1,91 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `Special and sample values for WGSL scalar types`;import { assert } from '../../common/util/util.js';
+import { uint32ToFloat32 } from '../util/conversion.js';
+
+/** Returns an array of subnormal f32 numbers.
+ * Subnormals are non-zero finite numbers with the minimum representable
+ * exponent.
+ */
+export function subnormalF32Examples() {
+ // The results, as uint32 values.
+ const result_as_bits = [];
+
+ const max_mantissa = 0x7f_ffff;
+ const sign_bits = [0, 0x8000_0000];
+ for (const sign_bit of sign_bits) {
+ // exponent bits must be zero.
+ const sign_and_exponent = sign_bit;
+
+ // Set all bits
+ result_as_bits.push(sign_and_exponent | max_mantissa);
+
+ // Set each of the lower bits individually.
+ for (let lower_bits = 1; lower_bits <= max_mantissa; lower_bits <<= 1) {
+ result_as_bits.push(sign_and_exponent | lower_bits);
+ }
+ }
+ assert(
+ result_as_bits.length === 2 * (1 + 23),
+ 'subnormal number sample count is ' + result_as_bits.length.toString()
+ );
+ return result_as_bits.map((u) => uint32ToFloat32(u));
+}
+
+/** Returns an array of normal f32 numbers.
+ * Normal numbers are not: zero, Nan, infinity, subnormal.
+ */
+export function normalF32Examples() {
+ const result = [1.0, -2.0];
+
+ const max_mantissa_as_bits = 0x7f_ffff;
+ const min_exponent_as_bits = 0x0080_0000;
+ const max_exponent_as_bits = 0x7f00_0000; // Max normal exponent
+ const sign_bits = [0, 0x8000_0000];
+ for (const sign_bit of sign_bits) {
+ for (let e = min_exponent_as_bits; e <= max_exponent_as_bits; e += min_exponent_as_bits) {
+ const sign_and_exponent = sign_bit | e;
+
+ // Set zero mantissa bits
+ result.push(uint32ToFloat32(sign_and_exponent));
+ // Set all mantissa bits
+ result.push(uint32ToFloat32(sign_and_exponent | max_mantissa_as_bits));
+
+ // Set each of the lower bits individually.
+ for (let lower_bits = 1; lower_bits <= max_mantissa_as_bits; lower_bits <<= 1) {
+ result.push(uint32ToFloat32(sign_and_exponent | lower_bits));
+ }
+ }
+ }
+ assert(
+ result.length === 2 + 2 * 254 * 25,
+ 'normal number sample count is ' + result.length.toString()
+ );
+ return result;
+}
+
+/** Returns an array of 32-bit NaNs, as Uint32 bit patterns.
+ * NaNs have: maximum exponent, but the mantissa is not zero.
+ */
+export function nanF32BitsExamples() {
+ const result = [];
+ const exponent_bit = 0x7f80_0000;
+ const sign_bits = [0, 0x8000_0000];
+ for (const sign_bit of sign_bits) {
+ const sign_and_exponent = sign_bit | exponent_bit;
+ const bits = sign_and_exponent | 0x40_0000;
+ // Only the most significant bit of the mantissa is set.
+ result.push(bits);
+
+ // Quiet and signalling NaNs differ based on the most significant bit
+ // of the mantissa. Try both.
+ for (const quiet_signalling of [0, 0x40_0000]) {
+ // Set each of the lower bits.
+ for (let lower_bits = 1; lower_bits < 0x40_0000; lower_bits <<= 1) {
+ const bits = sign_and_exponent | quiet_signalling | lower_bits;
+ result.push(bits);
+ }
+ }
+ }
+ return result;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/binary_stream.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/binary_stream.js
new file mode 100644
index 0000000000..cd90821ea4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/binary_stream.js
@@ -0,0 +1,213 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../common/util/util.js';import { float16ToUint16, uint16ToFloat16 } from './conversion.js';
+import { align } from './math.js';
+
+/**
+ * BinaryStream is a utility to efficiently encode and decode numbers to / from a Uint8Array.
+ * BinaryStream uses a number of internal typed arrays to avoid small array allocations when reading
+ * and writing.
+ */
+export default class BinaryStream {
+ /**
+ * Constructor
+ * @param buffer the buffer to read from / write to. Array length must be a multiple of 8 bytes.
+ */
+ constructor(buffer) {
+ this.offset = 0;
+ this.view = new DataView(buffer);
+ }
+
+ /** buffer() returns the stream's buffer sliced to the 8-byte rounded read or write offset */
+ buffer() {
+ return new Uint8Array(this.view.buffer, 0, align(this.offset, 8));
+ }
+
+ /** writeBool() writes a boolean as 255 or 0 to the buffer at the next byte offset */
+ writeBool(value) {
+ this.view.setUint8(this.offset++, value ? 255 : 0);
+ }
+
+ /** readBool() reads a boolean from the buffer at the next byte offset */
+ readBool() {
+ const val = this.view.getUint8(this.offset++);
+ assert(val === 0 || val === 255);
+ return val !== 0;
+ }
+
+ /** writeU8() writes a uint8 to the buffer at the next byte offset */
+ writeU8(value) {
+ this.view.setUint8(this.offset++, value);
+ }
+
+ /** readU8() reads a uint8 from the buffer at the next byte offset */
+ readU8() {
+ return this.view.getUint8(this.offset++);
+ }
+
+ /** writeU16() writes a uint16 to the buffer at the next 16-bit aligned offset */
+ writeU16(value) {
+ this.view.setUint16(this.alignedOffset(2), value, /* littleEndian */true);
+ }
+
+ /** readU16() reads a uint16 from the buffer at the next 16-bit aligned offset */
+ readU16() {
+ return this.view.getUint16(this.alignedOffset(2), /* littleEndian */true);
+ }
+
+ /** writeU32() writes a uint32 to the buffer at the next 32-bit aligned offset */
+ writeU32(value) {
+ this.view.setUint32(this.alignedOffset(4), value, /* littleEndian */true);
+ }
+
+ /** readU32() reads a uint32 from the buffer at the next 32-bit aligned offset */
+ readU32() {
+ return this.view.getUint32(this.alignedOffset(4), /* littleEndian */true);
+ }
+
+ /** writeI8() writes a int8 to the buffer at the next byte offset */
+ writeI8(value) {
+ this.view.setInt8(this.offset++, value);
+ }
+
+ /** readI8() reads a int8 from the buffer at the next byte offset */
+ readI8() {
+ return this.view.getInt8(this.offset++);
+ }
+
+ /** writeI16() writes a int16 to the buffer at the next 16-bit aligned offset */
+ writeI16(value) {
+ this.view.setInt16(this.alignedOffset(2), value, /* littleEndian */true);
+ }
+
+ /** readI16() reads a int16 from the buffer at the next 16-bit aligned offset */
+ readI16() {
+ return this.view.getInt16(this.alignedOffset(2), /* littleEndian */true);
+ }
+
+ /** writeI32() writes a int32 to the buffer at the next 32-bit aligned offset */
+ writeI32(value) {
+ this.view.setInt32(this.alignedOffset(4), value, /* littleEndian */true);
+ }
+
+ /** readI32() reads a int32 from the buffer at the next 32-bit aligned offset */
+ readI32() {
+ return this.view.getInt32(this.alignedOffset(4), /* littleEndian */true);
+ }
+
+ /** writeF16() writes a float16 to the buffer at the next 16-bit aligned offset */
+ writeF16(value) {
+ this.writeU16(float16ToUint16(value));
+ }
+
+ /** readF16() reads a float16 from the buffer at the next 16-bit aligned offset */
+ readF16() {
+ return uint16ToFloat16(this.readU16());
+ }
+
+ /** writeF32() writes a float32 to the buffer at the next 32-bit aligned offset */
+ writeF32(value) {
+ this.view.setFloat32(this.alignedOffset(4), value, /* littleEndian */true);
+ }
+
+ /** readF32() reads a float32 from the buffer at the next 32-bit aligned offset */
+ readF32() {
+ return this.view.getFloat32(this.alignedOffset(4), /* littleEndian */true);
+ }
+
+ /** writeF64() writes a float64 to the buffer at the next 64-bit aligned offset */
+ writeF64(value) {
+ this.view.setFloat64(this.alignedOffset(8), value, /* littleEndian */true);
+ }
+
+ /** readF64() reads a float64 from the buffer at the next 64-bit aligned offset */
+ readF64() {
+ return this.view.getFloat64(this.alignedOffset(8), /* littleEndian */true);
+ }
+
+ /**
+ * writeString() writes a length-prefixed UTF-16 string to the buffer at the next 32-bit aligned
+ * offset
+ */
+ writeString(value) {
+ this.writeU32(value.length);
+ for (let i = 0; i < value.length; i++) {
+ this.writeU16(value.charCodeAt(i));
+ }
+ }
+
+ /**
+ * readString() writes a length-prefixed UTF-16 string from the buffer at the next 32-bit aligned
+ * offset
+ */
+ readString() {
+ const len = this.readU32();
+ const codes = new Array(len);
+ for (let i = 0; i < len; i++) {
+ codes[i] = this.readU16();
+ }
+ return String.fromCharCode(...codes);
+ }
+
+ /**
+ * writeArray() writes a length-prefixed array of T elements to the buffer at the next 32-bit
+ * aligned offset, using the provided callback to write the individual elements
+ */
+ writeArray(value, writeElement) {
+ this.writeU32(value.length);
+ for (const element of value) {
+ writeElement(this, element);
+ }
+ }
+
+ /**
+ * readArray() reads a length-prefixed array of T elements from the buffer at the next 32-bit
+ * aligned offset, using the provided callback to read the individual elements
+ */
+ readArray(readElement) {
+ const len = this.readU32();
+ const array = new Array(len);
+ for (let i = 0; i < len; i++) {
+ array[i] = readElement(this);
+ }
+ return array;
+ }
+
+ /**
+ * writeCond() writes the boolean condition `cond` to the buffer, then either calls if_true if
+ * `cond` is true, otherwise if_false
+ */
+ writeCond(cond, fns) {
+ this.writeBool(cond);
+ if (cond) {
+ return fns.if_true();
+ } else {
+ return fns.if_false();
+ }
+ }
+
+ /**
+ * readCond() reads a boolean condition from the buffer, then either calls if_true if
+ * the condition was is true, otherwise if_false
+ */
+ readCond(fns) {
+ if (this.readBool()) {
+ return fns.if_true();
+ } else {
+ return fns.if_false();
+ }
+ }
+
+ /**
+ * alignedOffset() aligns this.offset to `bytes`, then increments this.offset by `bytes`.
+ * @returns the old offset aligned to the next multiple of `bytes`.
+ */
+ alignedOffset(bytes) {
+ const aligned = align(this.offset, bytes);
+ this.offset = aligned + bytes;
+ return aligned;
+ }
+
+
+
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/buffer.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/buffer.js
new file mode 100644
index 0000000000..0ce8bd858d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/buffer.js
@@ -0,0 +1,23 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { memcpy } from '../../common/util/util.js';import { align } from './math.js';
+
+/**
+ * Creates a buffer with the contents of some TypedArray.
+ * The buffer size will always be aligned to 4 as we set mappedAtCreation === true when creating the
+ * buffer.
+ */
+export function makeBufferWithContents(
+device,
+dataArray,
+usage)
+{
+ const buffer = device.createBuffer({
+ mappedAtCreation: true,
+ size: align(dataArray.byteLength, 4),
+ usage
+ });
+ memcpy({ src: dataArray }, { dst: buffer.getMappedRange() });
+ buffer.unmap();
+ return buffer;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/check_contents.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/check_contents.js
new file mode 100644
index 0000000000..645d78de55
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/check_contents.js
@@ -0,0 +1,272 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // MAINTENANCE_TODO: The "checkThingTrue" naming is confusing; these must be used with `expectOK`
+// or the result is dropped on the floor. Rename these to things like `typedArrayIsOK`(??) to
+// make it clearer.
+// MAINTENANCE_TODO: Also, audit to make sure we aren't dropping any on the floor. Consider a
+// no-ignored-return lint check if we can find one that we can use.
+import { assert,
+ErrorWithExtra,
+iterRange,
+range } from
+
+
+'../../common/util/util.js';
+import { Float16Array } from '../../external/petamoriken/float16/float16.js';
+
+import { generatePrettyTable } from './pretty_diff_tables.js';
+
+/** Generate an expected value at `index`, to test for equality with the actual value. */
+
+/** Check whether the actual `value` at `index` is as expected. */
+
+/**
+ * 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.
+ */
+
+
+
+
+
+
+
+
+
+
+/**
+ * Check whether two `TypedArray`s have equal contents.
+ * Returns `undefined` if the check passes, or an `Error` if not.
+ */
+export function checkElementsEqual(
+actual,
+expected)
+{
+ assert(actual.constructor === expected.constructor, 'TypedArray type mismatch');
+ assert(actual.length === expected.length, 'size mismatch');
+
+ let failedElementsFirstMaybe = undefined;
+ /** Sparse array with `true` for elements that failed. */
+ const failedElements = [];
+ for (let i = 0; i < actual.length; ++i) {
+ if (actual[i] !== expected[i]) {
+ failedElementsFirstMaybe ??= i;
+ failedElements[i] = true;
+ }
+ }
+
+ if (failedElementsFirstMaybe === undefined) {
+ return undefined;
+ }
+
+ const failedElementsFirst = failedElementsFirstMaybe;
+ return failCheckElements({
+ actual,
+ failedElements,
+ failedElementsFirst,
+ predicatePrinter: [{ leftHeader: 'expected ==', getValueForCell: (index) => expected[index] }]
+ });
+}
+
+/**
+ * Check whether each value in a `TypedArray` is between the two corresponding "expected" values
+ * (either `a(i) <= actual[i] <= b(i)` or `a(i) >= actual[i] => b(i)`).
+ */
+export function checkElementsBetween(
+actual,
+expected)
+{
+ const error = checkElementsPassPredicate(
+ actual,
+ (index, value) =>
+ value >= Math.min(expected[0](index), expected[1](index)) &&
+ value <= Math.max(expected[0](index), expected[1](index)),
+ {
+ predicatePrinter: [
+ { leftHeader: 'between', getValueForCell: (index) => expected[0](index) },
+ { leftHeader: 'and', getValueForCell: (index) => expected[1](index) }]
+
+ }
+ );
+ // If there was an error, extend it with additional extras.
+ return error ? new ErrorWithExtra(error, () => ({ expected })) : undefined;
+}
+
+/**
+ * Check whether each value in a `TypedArray` is equal to one of the two corresponding "expected"
+ * values (either `actual[i] === a[i]` or `actual[i] === b[i]`)
+ */
+export function checkElementsEqualEither(
+actual,
+expected)
+{
+ const error = checkElementsPassPredicate(
+ actual,
+ (index, value) => value === expected[0][index] || value === expected[1][index],
+ {
+ predicatePrinter: [
+ { leftHeader: 'either', getValueForCell: (index) => expected[0][index] },
+ { leftHeader: 'or', getValueForCell: (index) => expected[1][index] }]
+
+ }
+ );
+ // If there was an error, extend it with additional extras.
+ return error ? new ErrorWithExtra(error, () => ({ expected })) : undefined;
+}
+
+/**
+ * Check whether a `TypedArray`'s contents equal the values produced by a generator function.
+ * Returns `undefined` if the check passes, or an `Error` if not.
+ *
+ * ```text
+ * Array had unexpected contents at indices 2 through 19.
+ * Starting at index 1:
+ * actual == 0x: 00 fe ff 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00
+ * failed -> xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
+ * expected == 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ * ```
+ *
+ * ```text
+ * Array had unexpected contents at indices 2 through 29.
+ * Starting at index 1:
+ * actual == 0.000 -2.000e+100 -1.000e+100 0.000 1.000e+100 2.000e+100 3.000e+100 4.000e+100 5.000e+100 6.000e+100 7.000e+100 ...
+ * failed -> xx xx xx xx xx xx xx xx xx ...
+ * expected == 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 ...
+ * ```
+ */
+export function checkElementsEqualGenerated(
+actual,
+generator)
+{
+ let failedElementsFirstMaybe = undefined;
+ /** Sparse array with `true` for elements that failed. */
+ const failedElements = [];
+ for (let i = 0; i < actual.length; ++i) {
+ if (actual[i] !== generator(i)) {
+ failedElementsFirstMaybe ??= i;
+ failedElements[i] = true;
+ }
+ }
+
+ if (failedElementsFirstMaybe === undefined) {
+ return undefined;
+ }
+
+ const failedElementsFirst = failedElementsFirstMaybe;
+ const error = failCheckElements({
+ actual,
+ failedElements,
+ failedElementsFirst,
+ predicatePrinter: [{ leftHeader: 'expected ==', getValueForCell: (index) => generator(index) }]
+ });
+ // Add more extras to the error.
+ return new ErrorWithExtra(error, () => ({ generator }));
+}
+
+/**
+ * Check whether a `TypedArray`'s values pass the provided predicate function.
+ * Returns `undefined` if the check passes, or an `Error` if not.
+ */
+export function checkElementsPassPredicate(
+actual,
+predicate,
+{ predicatePrinter })
+{
+ let failedElementsFirstMaybe = undefined;
+ /** Sparse array with `true` for elements that failed. */
+ const failedElements = [];
+ for (let i = 0; i < actual.length; ++i) {
+ if (!predicate(i, actual[i])) {
+ failedElementsFirstMaybe ??= i;
+ failedElements[i] = true;
+ }
+ }
+
+ if (failedElementsFirstMaybe === undefined) {
+ return undefined;
+ }
+
+ const failedElementsFirst = failedElementsFirstMaybe;
+ return failCheckElements({ actual, failedElements, failedElementsFirst, predicatePrinter });
+}
+
+
+
+
+
+
+
+
+/**
+ * Implements the failure case of some checkElementsX helpers above. This allows those functions to
+ * implement their checks directly without too many function indirections in between.
+ *
+ * Note: Separating this into its own function significantly speeds up the non-error case in
+ * Chromium (though this may be V8-specific behavior).
+ */
+function failCheckElements({
+ actual,
+ failedElements,
+ failedElementsFirst,
+ predicatePrinter
+}) {
+ const size = actual.length;
+ const ctor = actual.constructor;
+ const printAsFloat = ctor === Float16Array || ctor === Float32Array || ctor === Float64Array;
+
+ const failedElementsLast = failedElements.length - 1;
+
+ // Include one extra non-failed element at the beginning and end (if they exist), for context.
+ const printElementsStart = Math.max(0, failedElementsFirst - 1);
+ const printElementsEnd = Math.min(size, failedElementsLast + 2);
+ const printElementsCount = printElementsEnd - printElementsStart;
+
+ const numberToString = printAsFloat ?
+ (n) => n.toPrecision(4) :
+ (n) => intToPaddedHex(n, { byteLength: ctor.BYTES_PER_ELEMENT });
+ const numberPrefix = printAsFloat ? '' : '0x:';
+
+ const printActual = actual.subarray(printElementsStart, printElementsEnd);
+ const printExpected = [];
+ if (predicatePrinter) {
+ for (const { leftHeader, getValueForCell: cell } of predicatePrinter) {
+ printExpected.push(
+ function* () {
+ yield* [leftHeader, ''];
+ yield* iterRange(printElementsCount, (i) => cell(printElementsStart + i));
+ }()
+ );
+ }
+ }
+
+ const printFailedValueMarkers = function* () {
+ yield* ['failed ->', ''];
+ yield* range(printElementsCount, (i) => failedElements[printElementsStart + i] ? 'xx' : '');
+ }();
+
+ const opts = {
+ fillToWidth: 120,
+ numberToString
+ };
+ const msg = `Array had unexpected contents at indices ${failedElementsFirst} through ${failedElementsLast}.
+ Starting at index ${printElementsStart}:
+${generatePrettyTable(opts, [
+ ['actual ==', numberPrefix, ...printActual],
+ printFailedValueMarkers,
+ ...printExpected]
+ )}`;
+ return new ErrorWithExtra(msg, () => ({
+ actual: actual.slice()
+ }));
+}
+
+// Helper helpers
+
+/** Convert an integral `number` into a hex string, padded to the specified `byteLength`. */
+function intToPaddedHex(number, { byteLength }) {
+ 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;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/color_space_conversion.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/color_space_conversion.js
new file mode 100644
index 0000000000..7e85d4ed9b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/color_space_conversion.js
@@ -0,0 +1,265 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../common/util/util.js';import { multiplyMatrices } from './math.js';
+
+// These color space conversion function definitions are copied directly from
+// CSS Color Module Level 4 Sample Code: https://drafts.csswg.org/css-color/#color-conversion-code
+// *EXCEPT* the conversion matrices are replaced with exact rational forms computed here:
+// https://github.com/kainino0x/exact_css_xyz_matrices
+// using this Rust crate: https://crates.io/crates/rgb_derivation
+// as described for sRGB on this page: https://mina86.com/2019/srgb-xyz-matrix/
+// but using the numbers from the CSS spec: https://www.w3.org/TR/css-color-4/#predefined
+
+// Sample code for color conversions
+// Conversion can also be done using ICC profiles and a Color Management System
+// For clarity, a library is used for matrix multiplication (multiply-matrices.js)
+
+// sRGB-related functions
+
+/**
+ * convert an array of sRGB values
+ * where in-gamut values are in the range [0 - 1]
+ * to linear light (un-companded) form.
+ * https://en.wikipedia.org/wiki/SRGB
+ * Extended transfer function:
+ * for negative values, linear portion is extended on reflection of axis,
+ * then reflected power function is used.
+ */
+function lin_sRGB(RGB) {
+ return RGB.map((val) => {
+ const sign = val < 0 ? -1 : 1;
+ const abs = Math.abs(val);
+
+ if (abs < 0.04045) {
+ return val / 12.92;
+ }
+
+ return sign * Math.pow((abs + 0.055) / 1.055, 2.4);
+ });
+}
+
+/**
+ * convert an array of linear-light sRGB values in the range 0.0-1.0
+ * to gamma corrected form
+ * https://en.wikipedia.org/wiki/SRGB
+ * Extended transfer function:
+ * For negative values, linear portion extends on reflection
+ * of axis, then uses reflected pow below that
+ */
+function gam_sRGB(RGB) {
+ return RGB.map((val) => {
+ const sign = val < 0 ? -1 : 1;
+ const abs = Math.abs(val);
+
+ if (abs > 0.0031308) {
+ return sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055);
+ }
+
+ return 12.92 * val;
+ });
+}
+
+/**
+ * convert an array of linear-light sRGB values to CIE XYZ
+ * using sRGB's own white, D65 (no chromatic adaptation)
+ */
+function lin_sRGB_to_XYZ(rgb) {
+
+ const M = [
+ [506752 / 1228815, 87881 / 245763, 12673 / 70218],
+ [87098 / 409605, 175762 / 245763, 12673 / 175545],
+ [7918 / 409605, 87881 / 737289, 1001167 / 1053270]];
+
+ return multiplyMatrices(M, rgb);
+}
+
+/**
+ * convert XYZ to linear-light sRGB
+ * using sRGB's own white, D65 (no chromatic adaptation)
+ */
+function XYZ_to_lin_sRGB(XYZ) {
+
+ const M = [
+ [12831 / 3959, -329 / 214, -1974 / 3959],
+ [-851781 / 878810, 1648619 / 878810, 36519 / 878810],
+ [705 / 12673, -2585 / 12673, 705 / 667]];
+
+
+ return multiplyMatrices(M, XYZ);
+}
+
+// display-p3-related functions
+
+/**
+ * convert an array of display-p3 RGB values in the range 0.0 - 1.0
+ * to linear light (un-companded) form.
+ */
+function lin_P3(RGB) {
+ return lin_sRGB(RGB); // same as sRGB
+}
+
+/**
+ * convert an array of linear-light display-p3 RGB in the range 0.0-1.0
+ * to gamma corrected form
+ */
+function gam_P3(RGB) {
+ return gam_sRGB(RGB); // same as sRGB
+}
+
+/**
+ * convert an array of linear-light display-p3 values to CIE XYZ
+ * using display-p3's D65 (no chromatic adaptation)
+ */
+function lin_P3_to_XYZ(rgb) {
+
+ const M = [
+ [608311 / 1250200, 189793 / 714400, 198249 / 1000160],
+ [35783 / 156275, 247089 / 357200, 198249 / 2500400],
+ [0 / 1, 32229 / 714400, 5220557 / 5000800]];
+
+
+ return multiplyMatrices(M, rgb);
+}
+
+/**
+ * convert XYZ to linear-light P3
+ * using display-p3's own white, D65 (no chromatic adaptation)
+ */
+function XYZ_to_lin_P3(XYZ) {
+
+ const M = [
+ [446124 / 178915, -333277 / 357830, -72051 / 178915],
+ [-14852 / 17905, 63121 / 35810, 423 / 17905],
+ [11844 / 330415, -50337 / 660830, 316169 / 330415]];
+
+
+ return multiplyMatrices(M, XYZ);
+}
+
+/**
+ * @returns the converted pixels in `{R: number, G: number, B: number, A: number}`.
+ *
+ * Follow conversion steps in CSS Color Module Level 4
+ * https://drafts.csswg.org/css-color/#predefined-to-predefined
+ * display-p3 and sRGB share the same white points.
+ */
+export function displayP3ToSrgb(pixel)
+
+
+
+
+{
+ assert(
+ pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
+ 'color space conversion requires all of R, G and B components'
+ );
+
+ let rgbVec = [pixel.R, pixel.G, pixel.B];
+ rgbVec = lin_P3(rgbVec);
+ let rgbMatrix = [[rgbVec[0]], [rgbVec[1]], [rgbVec[2]]];
+ rgbMatrix = XYZ_to_lin_sRGB(lin_P3_to_XYZ(rgbMatrix));
+ 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;
+}
+/**
+ * @returns the converted pixels in `{R: number, G: number, B: number, A: number}`.
+ *
+ * Follow conversion steps in CSS Color Module Level 4
+ * https://drafts.csswg.org/css-color/#predefined-to-predefined
+ * display-p3 and sRGB share the same white points.
+ */
+export function srgbToDisplayP3(pixel)
+
+
+
+
+{
+ assert(
+ pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
+ 'color space conversion requires all of R, G and B components'
+ );
+
+ let rgbVec = [pixel.R, pixel.G, pixel.B];
+ rgbVec = lin_sRGB(rgbVec);
+ let rgbMatrix = [[rgbVec[0]], [rgbVec[1]], [rgbVec[2]]];
+ rgbMatrix = XYZ_to_lin_P3(lin_sRGB_to_XYZ(rgbMatrix));
+ 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;
+}
+
+
+
+
+
+
+
+
+/**
+ * Returns a function which applies the specified colorspace/premultiplication conversion.
+ * Does not clamp, so may return values outside of the `dstColorSpace` gamut, due to either
+ * color space conversion or alpha premultiplication.
+ */
+export function makeInPlaceColorConversion({
+ srcPremultiplied,
+ dstPremultiplied,
+ srcColorSpace = 'srgb',
+ dstColorSpace = 'srgb'
+
+
+
+
+
+}) {
+ const requireColorSpaceConversion = srcColorSpace !== dstColorSpace;
+ const requireUnpremultiplyAlpha =
+ srcPremultiplied && (requireColorSpaceConversion || srcPremultiplied !== dstPremultiplied);
+ const requirePremultiplyAlpha =
+ dstPremultiplied && (requireColorSpaceConversion || srcPremultiplied !== dstPremultiplied);
+
+ return (rgba) => {
+ assert(rgba.A >= 0.0 && rgba.A <= 1.0, 'rgba.A out of bounds');
+
+ if (requireUnpremultiplyAlpha) {
+ if (rgba.A !== 0.0) {
+ rgba.R /= rgba.A;
+ rgba.G /= rgba.A;
+ rgba.B /= rgba.A;
+ } else {
+ assert(
+ rgba.R === 0.0 && rgba.G === 0.0 && rgba.B === 0.0 && rgba.A === 0.0,
+ 'Unpremultiply ops with alpha value 0.0 requires all channels equals to 0.0'
+ );
+ }
+ }
+ // It's possible RGB are now > 1.
+ // 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);
+ } else {
+ unreachable();
+ }
+ }
+ // Now RGB may also be negative if the src gamut is larger than the dst gamut.
+
+ if (requirePremultiplyAlpha) {
+ rgba.R *= rgba.A;
+ rgba.G *= rgba.A;
+ rgba.B *= rgba.A;
+ }
+ };
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/command_buffer_maker.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/command_buffer_maker.js
new file mode 100644
index 0000000000..7c54c35c2d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/command_buffer_maker.js
@@ -0,0 +1,85 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const kRenderEncodeTypes = ['render pass', 'render bundle'];
+
+export const kProgrammableEncoderTypes = ['compute pass', ...kRenderEncodeTypes];
+
+export const kEncoderTypes = ['non-pass', ...kProgrammableEncoderTypes];
+
+
+// Look up the type of the encoder based on `T`. If `T` is a union, this will be too!
+
+
+
+
+
+
+
+/** See {@link webgpu/api/validation/validation_test.ValidationTest.createEncoder |
+ * GPUTest.createEncoder()}. */
+export class CommandBufferMaker {
+ /** `GPU___Encoder` for recording commands into. */
+ // Look up the type of the encoder based on `T`. If `T` is a union, this will be too!
+
+
+ /**
+ * Finish any passes, finish and record any bundles, and finish/return the command buffer. Any
+ * errors are ignored and the GPUCommandBuffer (which may be an error buffer) is returned.
+ */
+
+
+ /**
+ * Finish any passes, finish and record any bundles, and finish/return the command buffer.
+ * Checks for validation errors in (only) the appropriate finish call.
+ */
+
+
+ /**
+ * Finish the command buffer and submit it. Checks for validation errors in either the submit or
+ * the appropriate finish call, depending on the state of a resource used in the encoding.
+ */
+
+
+
+
+
+ /**
+ * `validateFinishAndSubmit()` based on the state of a resource in the command encoder.
+ * - `finish()` should fail if the resource is 'invalid'.
+ * - Only `submit()` should fail if the resource is 'destroyed'.
+ */
+
+
+ constructor(
+ t,
+ encoder,
+ finish)
+ {
+ // TypeScript introduces an intersection type here where we don't want one.
+ this.encoder = encoder;
+ this.finish = finish;
+
+ // Define extra methods like this, otherwise they get unbound when destructured, e.g.:
+ // const { encoder, validateFinishAndSubmit } = t.createEncoder(type);
+ // Alternatively, do not destructure, and call member functions, e.g.:
+ // const encoder = t.createEncoder(type);
+ // encoder.validateFinish(true);
+ this.validateFinish = (shouldSucceed) => {
+ return t.expectGPUError('validation', this.finish, !shouldSucceed);
+ };
+
+ this.validateFinishAndSubmit = (
+ shouldBeValid,
+ submitShouldSucceedIfValid) =>
+ {
+ const commandBuffer = this.validateFinish(shouldBeValid);
+ if (shouldBeValid) {
+ t.expectValidationError(() => t.queue.submit([commandBuffer]), !submitShouldSucceedIfValid);
+ }
+ };
+
+ this.validateFinishAndSubmitGivenState = (resourceState) => {
+ this.validateFinishAndSubmit(resourceState !== 'invalid', resourceState !== 'destroyed');
+ };
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/compare.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/compare.js
new file mode 100644
index 0000000000..af42e341c4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/compare.js
@@ -0,0 +1,472 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { getIsBuildingDataCache } from '../../common/framework/data_cache.js';import { Colors } from '../../common/util/colors.js';import { assert, unreachable } from '../../common/util/util.js';
+import {
+ deserializeExpectation,
+ serializeExpectation } from
+'../shader/execution/expression/case_cache.js';
+import { toComparator } from '../shader/execution/expression/expression.js';
+
+
+import { isFloatValue, Matrix, Scalar, Vector } from './conversion.js';
+import { FPInterval } from './floating_point.js';
+
+/** Comparison describes the result of a Comparator function. */
+
+
+
+
+
+
+// All Comparators must be serializable to be used in the CaseCache.
+// New Comparators should add a new entry to SerializableComparatorKind and
+// define functionality in serialize/deserializeComparator as needed.
+//
+// 'value' and 'packed' are internal framework Comparators that exist, so that
+// the whole Case type hierarchy doesn't need to be split into Serializable vs
+// non-Serializable paths. Passing them into the CaseCache will cause a runtime
+// error.
+// 'value' and 'packed' should never be used in .spec.ts files.
+//
+
+
+
+
+
+/** Comparator is a function that compares whether the provided value matches an expectation. */
+
+
+
+
+
+
+/** SerializedComparator is an enum of all the possible serialized comparator types. */var
+SerializedComparatorKind = /*#__PURE__*/function (SerializedComparatorKind) {SerializedComparatorKind[SerializedComparatorKind["AnyOf"] = 0] = "AnyOf";SerializedComparatorKind[SerializedComparatorKind["SkipUndefined"] = 1] = "SkipUndefined";SerializedComparatorKind[SerializedComparatorKind["AlwaysPass"] = 2] = "AlwaysPass";return SerializedComparatorKind;}(SerializedComparatorKind || {});
+
+
+
+
+
+/** serializeComparatorKind() serializes a ComparatorKind to a BinaryStream */
+function serializeComparatorKind(s, value) {
+ switch (value) {
+ case 'anyOf':
+ return s.writeU8(SerializedComparatorKind.AnyOf);
+ case 'skipUndefined':
+ return s.writeU8(SerializedComparatorKind.SkipUndefined);
+ case 'alwaysPass':
+ return s.writeU8(SerializedComparatorKind.AlwaysPass);
+ }
+}
+
+/** deserializeComparatorKind() deserializes a ComparatorKind from a BinaryStream */
+function deserializeComparatorKind(s) {
+ const kind = s.readU8();
+ switch (kind) {
+ case SerializedComparatorKind.AnyOf:
+ return 'anyOf';
+ case SerializedComparatorKind.SkipUndefined:
+ return 'skipUndefined';
+ case SerializedComparatorKind.AlwaysPass:
+ return 'alwaysPass';
+ default:
+ unreachable(`invalid serialized ComparatorKind: ${kind}`);
+ }
+}
+
+/**
+ * compares 'got' Value to 'expected' Value, returning the Comparison information.
+ * @param got the Value obtained from the test
+ * @param expected the expected Value
+ * @returns the comparison results
+ */
+// NOTE: This function does not use objectEquals, since that does not handle FP
+// specific corners cases correctly, i.e. that f64/f32/f16 are all considered
+// the same type for this comparison.
+function compareValue(got, expected) {
+ {
+ // Check types
+ const gTy = got.type;
+ const eTy = expected.type;
+ const bothFloatTypes = isFloatValue(got) && isFloatValue(expected);
+ if (gTy !== eTy && !bothFloatTypes) {
+ return {
+ matched: false,
+ got: `${Colors.red(gTy.toString())}(${got})`,
+ expected: `${Colors.red(eTy.toString())}(${expected})`
+ };
+ }
+ }
+
+ if (got instanceof Scalar) {
+ const g = got;
+ const e = expected;
+ const isFloat = g.type.kind === 'f64' || g.type.kind === 'f32' || g.type.kind === 'f16';
+ const matched =
+ isFloat && g.value === e.value || !isFloat && g.value === e.value;
+ return {
+ matched,
+ got: g.toString(),
+ expected: matched ? Colors.green(e.toString()) : Colors.red(e.toString())
+ };
+ }
+
+ if (got instanceof Vector) {
+ const e = expected;
+ const gLen = got.elements.length;
+ const eLen = e.elements.length;
+ let matched = gLen === eLen;
+ if (matched) {
+ // Iterating and calling compare instead of just using objectEquals to use the FP specific logic from above
+ matched = got.elements.every((_, i) => {
+ return compare(got.elements[i], e.elements[i]).matched;
+ });
+ }
+
+ return {
+ matched,
+ got: `${got.toString()}`,
+ expected: matched ? Colors.green(e.toString()) : Colors.red(e.toString())
+ };
+ }
+
+ if (got instanceof Matrix) {
+ const e = expected;
+ const gCols = got.type.cols;
+ const eCols = e.type.cols;
+ const gRows = got.type.rows;
+ const eRows = e.type.rows;
+ let matched = gCols === eCols && gRows === eRows;
+ if (matched) {
+ // Iterating and calling compare instead of just using objectEquals to use the FP specific logic from above
+ matched = got.elements.every((c, i) => {
+ return c.every((_, j) => {
+ return compare(got.elements[i][j], e.elements[i][j]).matched;
+ });
+ });
+ }
+
+ return {
+ matched,
+ got: `${got.toString()}`,
+ expected: matched ? Colors.green(e.toString()) : Colors.red(e.toString())
+ };
+ }
+
+ throw new Error(`unhandled type '${typeof got}`);
+}
+
+/**
+ * Tests it a 'got' Value is contained in 'expected' interval, returning the Comparison information.
+ * @param got the Value obtained from the test
+ * @param expected the expected FPInterval
+ * @returns the comparison results
+ */
+function compareInterval(got, expected) {
+ {
+ // Check type
+ const gTy = got.type;
+ if (!isFloatValue(got)) {
+ return {
+ matched: false,
+ got: `${Colors.red(gTy.toString())}(${got})`,
+ expected: `floating point value`
+ };
+ }
+ }
+
+ if (got instanceof Scalar) {
+ const g = got.value;
+ const matched = expected.contains(g);
+ return {
+ matched,
+ got: g.toString(),
+ expected: matched ? Colors.green(expected.toString()) : Colors.red(expected.toString())
+ };
+ }
+
+ // Vector results are currently not handled
+ throw new Error(`unhandled type '${typeof got}`);
+}
+
+/**
+ * Tests it a 'got' Value is contained in 'expected' vector, returning the Comparison information.
+ * @param got the Value obtained from the test, is expected to be a Vector
+ * @param expected the expected array of FPIntervals, one for each element of the vector
+ * @returns the comparison results
+ */
+function compareVector(got, expected) {
+ // Check got type
+ if (!(got instanceof Vector)) {
+ return {
+ matched: false,
+ got: `${Colors.red((typeof got).toString())}(${got})`,
+ expected: `Vector`
+ };
+ }
+
+ // Check element type
+ {
+ const gTy = got.type.elementType;
+ if (!isFloatValue(got.elements[0])) {
+ return {
+ matched: false,
+ got: `${Colors.red(gTy.toString())}(${got})`,
+ expected: `floating point elements`
+ };
+ }
+ }
+
+ if (got.elements.length !== expected.length) {
+ return {
+ matched: false,
+ got: `Vector of ${got.elements.length} elements`,
+ expected: `${expected.length} elements`
+ };
+ }
+
+ const results = got.elements.map((_, idx) => {
+ const g = got.elements[idx].value;
+ return { match: expected[idx].contains(g), index: idx };
+ });
+
+ const failures = results.filter((v) => !v.match).map((v) => v.index);
+ if (failures.length !== 0) {
+ const expected_string = expected.map((v, idx) =>
+ idx in failures ? Colors.red(`[${v}]`) : Colors.green(`[${v}]`)
+ );
+ return {
+ matched: false,
+ got: `[${got.elements}]`,
+ expected: `[${expected_string}]`
+ };
+ }
+
+ return {
+ matched: true,
+ got: `[${got.elements}]`,
+ expected: `[${Colors.green(expected.toString())}]`
+ };
+}
+
+// Utility to get arround not being able to nest `` blocks
+function convertArrayToString(m) {
+ return `[${m.join(',')}]`;
+}
+
+/**
+ * Tests it a 'got' Value is contained in 'expected' matrix, returning the Comparison information.
+ * @param got the Value obtained from the test, is expected to be a Matrix
+ * @param expected the expected array of array of FPIntervals, representing a column-major matrix
+ * @returns the comparison results
+ */
+function compareMatrix(got, expected) {
+ // Check got type
+ if (!(got instanceof Matrix)) {
+ return {
+ matched: false,
+ got: `${Colors.red((typeof got).toString())}(${got})`,
+ expected: `Matrix`
+ };
+ }
+
+ // Check element type
+ {
+ const gTy = got.type.elementType;
+ if (!isFloatValue(got.elements[0][0])) {
+ return {
+ matched: false,
+ got: `${Colors.red(gTy.toString())}(${got})`,
+ expected: `floating point elements`
+ };
+ }
+ }
+
+ // Check matrix dimensions
+ {
+ const gCols = got.elements.length;
+ const gRows = got.elements[0].length;
+ const eCols = expected.length;
+ const eRows = expected[0].length;
+
+ if (gCols !== eCols || gRows !== eRows) {
+ assert(false);
+ return {
+ matched: false,
+ got: `Matrix of ${gCols}x${gRows} elements`,
+ expected: `Matrix of ${eCols}x${eRows} elements`
+ };
+ }
+ }
+
+ // Check that got values fall in expected intervals
+ let matched = true;
+ const expected_strings = [...Array(got.elements.length)].map((_) => [
+ ...Array(got.elements[0].length)]
+ );
+
+ got.elements.forEach((c, i) => {
+ c.forEach((r, j) => {
+ const g = r.value;
+ if (expected[i][j].contains(g)) {
+ expected_strings[i][j] = Colors.green(`[${expected[i][j]}]`);
+ } else {
+ matched = false;
+ expected_strings[i][j] = Colors.red(`[${expected[i][j]}]`);
+ }
+ });
+ });
+
+ return {
+ matched,
+ got: convertArrayToString(got.elements.map(convertArrayToString)),
+ expected: convertArrayToString(expected_strings.map(convertArrayToString))
+ };
+}
+
+/**
+ * compare() compares 'got' to 'expected', returning the Comparison information.
+ * @param got the result obtained from the test
+ * @param expected the expected result
+ * @returns the comparison results
+ */
+export function compare(
+got,
+expected)
+{
+ if (expected instanceof Array) {
+ if (expected[0] instanceof Array) {
+ expected = expected;
+ return compareMatrix(got, expected);
+ } else {
+ expected = expected;
+ return compareVector(got, expected);
+ }
+ }
+
+ if (expected instanceof FPInterval) {
+ return compareInterval(got, expected);
+ }
+
+ return compareValue(got, expected);
+}
+
+/** @returns a Comparator that checks whether a test value matches any of the provided options */
+export function anyOf(...expectations) {
+ const c = {
+ compare: (got) => {
+ const failed = new Set();
+ for (const e of expectations) {
+ const cmp = toComparator(e).compare(got);
+ if (cmp.matched) {
+ return cmp;
+ }
+ failed.add(cmp.expected);
+ }
+ return { matched: false, got: got.toString(), expected: [...failed].join(' or ') };
+ },
+ kind: 'anyOf'
+ };
+
+ if (getIsBuildingDataCache()) {
+ // If there's an active DataCache, and it supports storing, then append the
+ // Expectations to the result, so it can be serialized.
+ c.data = expectations;
+ }
+ return c;
+}
+
+/** @returns a Comparator that skips the test if the expectation is undefined */
+export function skipUndefined(expectation) {
+ const c = {
+ compare: (got) => {
+ if (expectation !== undefined) {
+ return toComparator(expectation).compare(got);
+ }
+ return { matched: true, got: got.toString(), expected: `Treating 'undefined' as Any` };
+ },
+ kind: 'skipUndefined'
+ };
+
+ if (expectation !== undefined && getIsBuildingDataCache()) {
+ // If there's an active DataCache, and it supports storing, then append the
+ // Expectation to the result, so it can be serialized.
+ c.data = expectation;
+ }
+ return c;
+}
+
+/**
+ * @returns a Comparator that always passes, used to test situations where the
+ * result of computation doesn't matter, but the fact it finishes is being
+ * tested.
+ */
+export function alwaysPass(msg = 'always pass') {
+ const c = {
+ compare: (got) => {
+ return { matched: true, got: got.toString(), expected: msg };
+ },
+ kind: 'alwaysPass'
+ };
+
+ if (getIsBuildingDataCache()) {
+ // If there's an active DataCache, and it supports storing, then append the
+ // message string to the result, so it can be serialized.
+ c.data = msg;
+ }
+ return c;
+}
+
+/** serializeComparator() serializes a Comparator to a BinaryStream */
+export function serializeComparator(s, c) {
+ serializeComparatorKind(s, c.kind);
+ switch (c.kind) {
+ case 'anyOf':
+ s.writeArray(c.data, serializeExpectation);
+ return;
+ case 'skipUndefined':
+ s.writeCond(c.data !== undefined, {
+ if_true: () => {
+ // defined data
+ serializeExpectation(s, c.data);
+ },
+ if_false: () => {
+
+ // undefined data
+ } });
+ return;
+ case 'alwaysPass':{
+ s.writeString(c.data);
+ return;
+ }
+ case 'value':
+ case 'packed':{
+ unreachable(`Serializing '${c.kind}' comparators is not allowed (${c})`);
+ break;
+ }
+ }
+ unreachable(`Unable serialize comparator '${c}'`);
+}
+
+/** deserializeComparator() deserializes a Comparator from a BinaryStream */
+export function deserializeComparator(s) {
+ const kind = deserializeComparatorKind(s);
+ switch (kind) {
+ case 'anyOf':
+ return anyOf(...s.readArray(deserializeExpectation));
+ case 'skipUndefined':
+ return s.readCond({
+ if_true: () => {
+ // defined data
+ return skipUndefined(deserializeExpectation(s));
+ },
+ if_false: () => {
+ // undefined data
+ return skipUndefined(undefined);
+ }
+ });
+ case 'alwaysPass':
+ return alwaysPass(s.readString());
+ }
+ unreachable(`Unable deserialize comparator '${s}'`);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/constants.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/constants.js
new file mode 100644
index 0000000000..b46a2b178b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/constants.js
@@ -0,0 +1,487 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { reinterpretU64AsF64, reinterpretF64AsU64,
+ reinterpretU32AsF32,
+ reinterpretU16AsF16 } from
+'./reinterpret.js';
+
+export const kBit = {
+ // Limits of int32
+ i32: {
+ positive: {
+ min: 0x0000_0000, // 0
+ max: 0x7fff_ffff // 2147483647
+ },
+ negative: {
+ min: 0x8000_0000, // -2147483648
+ max: 0x0000_0000 // 0
+ }
+ },
+
+ // Limits of uint32
+ u32: {
+ min: 0x0000_0000,
+ max: 0xffff_ffff
+ },
+
+ // Limits of f64
+ // Have to be stored as a BigInt hex value, since number is a f64 internally,
+ // so 64-bit hex values are not guaranteed to be precisely representable.
+ f64: {
+ positive: {
+ min: BigInt(0x0010_0000_0000_0000n),
+ max: BigInt(0x7fef_ffff_ffff_ffffn),
+ zero: BigInt(0x0000_0000_0000_0000n),
+ subnormal: {
+ min: BigInt(0x0000_0000_0000_0001n),
+ max: BigInt(0x000f_ffff_ffff_ffffn)
+ },
+ infinity: BigInt(0x7ff0_0000_0000_0000n),
+ nearest_max: BigInt(0x7fef_ffff_ffff_fffen),
+ less_than_one: BigInt(0x3fef_ffff_ffff_ffffn),
+ pi: {
+ whole: BigInt(0x4009_21fb_5444_2d18n),
+ three_quarters: BigInt(0x4002_d97c_7f33_21d2n),
+ half: BigInt(0x3ff9_21fb_5444_2d18n),
+ third: BigInt(0x3ff0_c152_382d_7365n),
+ quarter: BigInt(0x3fe9_21fb_5444_2d18n),
+ sixth: BigInt(0x3fe0_c152_382d_7365n)
+ },
+ e: BigInt(0x4005_bf0a_8b14_5769n)
+ },
+ negative: {
+ max: BigInt(0x8010_0000_0000_0000n),
+ min: BigInt(0xffef_ffff_ffff_ffffn),
+ zero: BigInt(0x8000_0000_0000_0000n),
+ subnormal: {
+ max: BigInt(0x8000_0000_0000_0001n),
+ min: BigInt(0x800f_ffff_ffff_ffffn)
+ },
+ infinity: BigInt(0xfff0_0000_0000_0000n),
+ nearest_min: BigInt(0xffef_ffff_ffff_fffen),
+ less_than_one: BigInt(0xbfef_ffff_ffff_ffffn),
+ pi: {
+ whole: BigInt(0xc009_21fb_5444_2d18n),
+ three_quarters: BigInt(0xc002_d97c_7f33_21d2n),
+ half: BigInt(0xbff9_21fb_5444_2d18n),
+ third: BigInt(0xbff0_c152_382d_7365n),
+ quarter: BigInt(0xbfe9_21fb_5444_2d18n),
+ sixth: BigInt(0xbfe0_c152_382d_7365n)
+ }
+ },
+ max_ulp: BigInt(0x7ca0_0000_0000_0000n)
+ },
+
+ // Limits of f32
+ f32: {
+ positive: {
+ min: 0x0080_0000,
+ max: 0x7f7f_ffff,
+ zero: 0x0000_0000,
+ subnormal: {
+ min: 0x0000_0001,
+ max: 0x007f_ffff
+ },
+ infinity: 0x7f80_0000,
+ nearest_max: 0x7f7f_fffe,
+ less_than_one: 0x3f7f_ffff,
+ pi: {
+ whole: 0x4049_0fdb,
+ three_quarters: 0x4016_cbe4,
+ half: 0x3fc9_0fdb,
+ third: 0x3f86_0a92,
+ quarter: 0x3f49_0fdb,
+ sixth: 0x3f06_0a92
+ },
+ e: 0x402d_f854
+ },
+ negative: {
+ max: 0x8080_0000,
+ min: 0xff7f_ffff,
+ zero: 0x8000_0000,
+ subnormal: {
+ max: 0x8000_0001,
+ min: 0x807f_ffff
+ },
+ infinity: 0xff80_0000,
+ nearest_min: 0xff7f_fffe,
+ less_than_one: 0xbf7f_ffff,
+ pi: {
+ whole: 0xc04_90fdb,
+ three_quarters: 0xc016_cbe4,
+ half: 0xbfc9_0fdb,
+ third: 0xbf86_0a92,
+ quarter: 0xbf49_0fdb,
+ sixth: 0xbf06_0a92
+ }
+ },
+ max_ulp: 0x7380_0000
+ },
+
+ // Limits of f16
+ f16: {
+ positive: {
+ min: 0x0400,
+ max: 0x7bff,
+ zero: 0x0000,
+ subnormal: {
+ min: 0x0001,
+ max: 0x03ff
+ },
+ infinity: 0x7c00,
+ nearest_max: 0x7bfe,
+ less_than_one: 0x3bff,
+ pi: {
+ whole: 0x4248,
+ three_quarters: 0x40b6,
+ half: 0x3e48,
+ third: 0x3c30,
+ quarter: 0x3a48,
+ sixth: 0x3830
+ },
+ e: 0x416f
+ },
+ negative: {
+ max: 0x8400,
+ min: 0xfbff,
+ zero: 0x8000,
+ subnormal: {
+ max: 0x8001,
+ min: 0x83ff
+ },
+ infinity: 0xfc00,
+ nearest_min: 0xfbfe,
+ less_than_one: 0xbbff,
+ pi: {
+ whole: 0xc248,
+ three_quarters: 0xc0b6,
+ half: 0xbe48,
+ third: 0xbc30,
+ quarter: 0xba48,
+ sixth: 0xb830
+ }
+ },
+ max_ulp: 0x5000
+ },
+
+ // Uint32 representation of power(2, n) n = {0, ..., 31}
+ // Stored as a JS `number`
+ // {to0, ..., to31} ie. {0, ..., 31}
+ powTwo: {
+ to0: 0x0000_0001,
+ to1: 0x0000_0002,
+ to2: 0x0000_0004,
+ to3: 0x0000_0008,
+ to4: 0x0000_0010,
+ to5: 0x0000_0020,
+ to6: 0x0000_0040,
+ to7: 0x0000_0080,
+ to8: 0x0000_0100,
+ to9: 0x0000_0200,
+ to10: 0x0000_0400,
+ to11: 0x0000_0800,
+ to12: 0x0000_1000,
+ to13: 0x0000_2000,
+ to14: 0x0000_4000,
+ to15: 0x0000_8000,
+ to16: 0x0001_0000,
+ to17: 0x0002_0000,
+ to18: 0x0004_0000,
+ to19: 0x0008_0000,
+ to20: 0x0010_0000,
+ to21: 0x0020_0000,
+ to22: 0x0040_0000,
+ to23: 0x0080_0000,
+ to24: 0x0100_0000,
+ to25: 0x0200_0000,
+ to26: 0x0400_0000,
+ to27: 0x0800_0000,
+ to28: 0x1000_0000,
+ to29: 0x2000_0000,
+ to30: 0x4000_0000,
+ to31: 0x8000_0000
+ },
+
+ // Int32 representation of of -1 * power(2, n) n = {0, ..., 31}
+ // Stored as a JS `number`
+ // {to0, ..., to31} ie. {0, ..., 31}
+ negPowTwo: {
+ to0: 0xffff_ffff,
+ to1: 0xffff_fffe,
+ to2: 0xffff_fffc,
+ to3: 0xffff_fff8,
+ to4: 0xffff_fff0,
+ to5: 0xffff_ffe0,
+ to6: 0xffff_ffc0,
+ to7: 0xffff_ff80,
+ to8: 0xffff_ff00,
+ to9: 0xffff_fe00,
+ to10: 0xffff_fc00,
+ to11: 0xffff_f800,
+ to12: 0xffff_f000,
+ to13: 0xffff_e000,
+ to14: 0xffff_c000,
+ to15: 0xffff_8000,
+ to16: 0xffff_0000,
+ to17: 0xfffe_0000,
+ to18: 0xfffc_0000,
+ to19: 0xfff8_0000,
+ to20: 0xfff0_0000,
+ to21: 0xffe0_0000,
+ to22: 0xffc0_0000,
+ to23: 0xff80_0000,
+ to24: 0xff00_0000,
+ to25: 0xfe00_0000,
+ to26: 0xfc00_0000,
+ to27: 0xf800_0000,
+ to28: 0xf000_0000,
+ to29: 0xe000_0000,
+ to30: 0xc000_0000,
+ to31: 0x8000_0000
+ }
+};
+
+export const kValue = {
+ // Limits of i32
+ i32: {
+ positive: {
+ min: 0,
+ max: 2147483647
+ },
+ negative: {
+ min: -2147483648,
+ max: 0
+ }
+ },
+
+ // Limits of u32
+ u32: {
+ min: 0,
+ max: 4294967295
+ },
+
+ // Limits of f64
+ f64: {
+ positive: {
+ min: reinterpretU64AsF64(kBit.f64.positive.min),
+ max: reinterpretU64AsF64(kBit.f64.positive.max),
+ zero: reinterpretU64AsF64(kBit.f64.positive.zero),
+ subnormal: {
+ min: reinterpretU64AsF64(kBit.f64.positive.subnormal.min),
+ max: reinterpretU64AsF64(kBit.f64.positive.subnormal.max)
+ },
+ infinity: reinterpretU64AsF64(kBit.f64.positive.infinity),
+ nearest_max: reinterpretU64AsF64(kBit.f64.positive.nearest_max),
+ less_than_one: reinterpretU64AsF64(kBit.f64.positive.less_than_one),
+ pi: {
+ whole: reinterpretU64AsF64(kBit.f64.positive.pi.whole),
+ three_quarters: reinterpretU64AsF64(kBit.f64.positive.pi.three_quarters),
+ half: reinterpretU64AsF64(kBit.f64.positive.pi.half),
+ third: reinterpretU64AsF64(kBit.f64.positive.pi.third),
+ quarter: reinterpretU64AsF64(kBit.f64.positive.pi.quarter),
+ sixth: reinterpretU64AsF64(kBit.f64.positive.pi.sixth)
+ },
+ e: reinterpretU64AsF64(kBit.f64.positive.e)
+ },
+ negative: {
+ max: reinterpretU64AsF64(kBit.f64.negative.max),
+ min: reinterpretU64AsF64(kBit.f64.negative.min),
+ zero: reinterpretU64AsF64(kBit.f64.negative.zero),
+ subnormal: {
+ max: reinterpretU64AsF64(kBit.f64.negative.subnormal.max),
+ min: reinterpretU64AsF64(kBit.f64.negative.subnormal.min)
+ },
+ infinity: reinterpretU64AsF64(kBit.f64.negative.infinity),
+ nearest_min: reinterpretU64AsF64(kBit.f64.negative.nearest_min),
+ less_than_one: reinterpretU64AsF64(kBit.f64.negative.less_than_one), // -0.999999940395
+ pi: {
+ whole: reinterpretU64AsF64(kBit.f64.negative.pi.whole),
+ three_quarters: reinterpretU64AsF64(kBit.f64.negative.pi.three_quarters),
+ half: reinterpretU64AsF64(kBit.f64.negative.pi.half),
+ third: reinterpretU64AsF64(kBit.f64.negative.pi.third),
+ quarter: reinterpretU64AsF64(kBit.f64.negative.pi.quarter),
+ sixth: reinterpretU64AsF64(kBit.f64.negative.pi.sixth)
+ }
+ },
+ max_ulp: reinterpretU64AsF64(kBit.f64.max_ulp)
+ },
+
+ // Limits of f32
+ f32: {
+ positive: {
+ min: reinterpretU32AsF32(kBit.f32.positive.min),
+ max: reinterpretU32AsF32(kBit.f32.positive.max),
+ zero: reinterpretU32AsF32(kBit.f32.positive.zero),
+ subnormal: {
+ min: reinterpretU32AsF32(kBit.f32.positive.subnormal.min),
+ max: reinterpretU32AsF32(kBit.f32.positive.subnormal.max)
+ },
+ infinity: reinterpretU32AsF32(kBit.f32.positive.infinity),
+
+ nearest_max: reinterpretU32AsF32(kBit.f32.positive.nearest_max),
+ less_than_one: reinterpretU32AsF32(kBit.f32.positive.less_than_one),
+ pi: {
+ whole: reinterpretU32AsF32(kBit.f32.positive.pi.whole),
+ three_quarters: reinterpretU32AsF32(kBit.f32.positive.pi.three_quarters),
+ half: reinterpretU32AsF32(kBit.f32.positive.pi.half),
+ third: reinterpretU32AsF32(kBit.f32.positive.pi.third),
+ quarter: reinterpretU32AsF32(kBit.f32.positive.pi.quarter),
+ sixth: reinterpretU32AsF32(kBit.f32.positive.pi.sixth)
+ },
+ e: reinterpretU32AsF32(kBit.f32.positive.e),
+ // The positive pipeline-overridable constant with the smallest magnitude
+ // which when cast to f32 will produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL.
+ first_non_castable_pipeline_override:
+ reinterpretU32AsF32(kBit.f32.positive.max) / 2 + 2 ** 127,
+ // The positive pipeline-overridable constant with the largest magnitude
+ // which when cast to f32 will not produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL
+ last_castable_pipeline_override: reinterpretU64AsF64(
+ reinterpretF64AsU64(reinterpretU32AsF32(kBit.f32.positive.max) / 2 + 2 ** 127) - BigInt(1)
+ )
+ },
+ negative: {
+ max: reinterpretU32AsF32(kBit.f32.negative.max),
+ min: reinterpretU32AsF32(kBit.f32.negative.min),
+ zero: reinterpretU32AsF32(kBit.f32.negative.zero),
+ subnormal: {
+ max: reinterpretU32AsF32(kBit.f32.negative.subnormal.max),
+ min: reinterpretU32AsF32(kBit.f32.negative.subnormal.min)
+ },
+ infinity: reinterpretU32AsF32(kBit.f32.negative.infinity),
+ nearest_min: reinterpretU32AsF32(kBit.f32.negative.nearest_min),
+ less_than_one: reinterpretU32AsF32(kBit.f32.negative.less_than_one), // -0.999999940395
+ pi: {
+ whole: reinterpretU32AsF32(kBit.f32.negative.pi.whole),
+ three_quarters: reinterpretU32AsF32(kBit.f32.negative.pi.three_quarters),
+ half: reinterpretU32AsF32(kBit.f32.negative.pi.half),
+ third: reinterpretU32AsF32(kBit.f32.negative.pi.third),
+ quarter: reinterpretU32AsF32(kBit.f32.negative.pi.quarter),
+ sixth: reinterpretU32AsF32(kBit.f32.negative.pi.sixth)
+ },
+ // The negative pipeline-overridable constant with the smallest magnitude
+ // which when cast to f32 will produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL.
+ first_non_castable_pipeline_override: -(
+ reinterpretU32AsF32(kBit.f32.positive.max) / 2 +
+ 2 ** 127),
+
+ // The negative pipeline-overridable constant with the largest magnitude
+ // which when cast to f32 will not produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL.
+ last_castable_pipeline_override: -reinterpretU64AsF64(
+ reinterpretF64AsU64(reinterpretU32AsF32(kBit.f32.positive.max) / 2 + 2 ** 127) - BigInt(1)
+ )
+ },
+ max_ulp: reinterpretU32AsF32(kBit.f32.max_ulp),
+ emax: 127
+ },
+
+ // Limits of i16
+ i16: {
+ positive: {
+ min: 0,
+ max: 32767
+ },
+ negative: {
+ min: -32768,
+ max: 0
+ }
+ },
+
+ // Limits of u16
+ u16: {
+ min: 0,
+ max: 65535
+ },
+
+ // Limits of f16
+ f16: {
+ positive: {
+ min: reinterpretU16AsF16(kBit.f16.positive.min),
+ max: reinterpretU16AsF16(kBit.f16.positive.max),
+ zero: reinterpretU16AsF16(kBit.f16.positive.zero),
+ subnormal: {
+ min: reinterpretU16AsF16(kBit.f16.positive.subnormal.min),
+ max: reinterpretU16AsF16(kBit.f16.positive.subnormal.max)
+ },
+ infinity: reinterpretU16AsF16(kBit.f16.positive.infinity),
+ nearest_max: reinterpretU16AsF16(kBit.f16.positive.nearest_max),
+ less_than_one: reinterpretU16AsF16(kBit.f16.positive.less_than_one),
+ pi: {
+ whole: reinterpretU16AsF16(kBit.f16.positive.pi.whole),
+ three_quarters: reinterpretU16AsF16(kBit.f16.positive.pi.three_quarters),
+ half: reinterpretU16AsF16(kBit.f16.positive.pi.half),
+ third: reinterpretU16AsF16(kBit.f16.positive.pi.third),
+ quarter: reinterpretU16AsF16(kBit.f16.positive.pi.quarter),
+ sixth: reinterpretU16AsF16(kBit.f16.positive.pi.sixth)
+ },
+ e: reinterpretU16AsF16(kBit.f16.positive.e),
+ // The positive pipeline-overridable constant with the smallest magnitude
+ // which when cast to f16 will produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL.
+ first_non_castable_pipeline_override:
+ reinterpretU16AsF16(kBit.f16.positive.max) / 2 + 2 ** 15,
+ // The positive pipeline-overridable constant with the largest magnitude
+ // which when cast to f16 will not produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL
+ last_castable_pipeline_override: reinterpretU64AsF64(
+ reinterpretF64AsU64(reinterpretU16AsF16(kBit.f16.positive.max) / 2 + 2 ** 15) - BigInt(1)
+ )
+ },
+ negative: {
+ max: reinterpretU16AsF16(kBit.f16.negative.max),
+ min: reinterpretU16AsF16(kBit.f16.negative.min),
+ zero: reinterpretU16AsF16(kBit.f16.negative.zero),
+ subnormal: {
+ max: reinterpretU16AsF16(kBit.f16.negative.subnormal.max),
+ min: reinterpretU16AsF16(kBit.f16.negative.subnormal.min)
+ },
+ infinity: reinterpretU16AsF16(kBit.f16.negative.infinity),
+ nearest_min: reinterpretU16AsF16(kBit.f16.negative.nearest_min),
+ less_than_one: reinterpretU16AsF16(kBit.f16.negative.less_than_one), // -0.9996
+ pi: {
+ whole: reinterpretU16AsF16(kBit.f16.negative.pi.whole),
+ three_quarters: reinterpretU16AsF16(kBit.f16.negative.pi.three_quarters),
+ half: reinterpretU16AsF16(kBit.f16.negative.pi.half),
+ third: reinterpretU16AsF16(kBit.f16.negative.pi.third),
+ quarter: reinterpretU16AsF16(kBit.f16.negative.pi.quarter),
+ sixth: reinterpretU16AsF16(kBit.f16.negative.pi.sixth)
+ },
+ // The negative pipeline-overridable constant with the smallest magnitude
+ // which when cast to f16 will produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL.
+ first_non_castable_pipeline_override: -(
+ reinterpretU16AsF16(kBit.f16.positive.max) / 2 +
+ 2 ** 15),
+
+ // The negative pipeline-overridable constant with the largest magnitude
+ // which when cast to f16 will not produce infinity. This comes from WGSL
+ // conversion rules and the rounding rules of WebIDL.
+ last_castable_pipeline_override: -reinterpretU64AsF64(
+ reinterpretF64AsU64(reinterpretU16AsF16(kBit.f16.positive.max) / 2 + 2 ** 15) - BigInt(1)
+ )
+ },
+ max_ulp: reinterpretU16AsF16(kBit.f16.max_ulp),
+ emax: 15
+ },
+
+ // Limits of i8
+ i8: {
+ positive: {
+ min: 0,
+ max: 127
+ },
+ negative: {
+ min: -128,
+ max: 0
+ }
+ },
+
+ // Limits of u8
+ u8: {
+ min: 0,
+ max: 255
+ }
+}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/conversion.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/conversion.js
new file mode 100644
index 0000000000..a56c09b3b7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/conversion.js
@@ -0,0 +1,1635 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { Colors } from '../../common/util/colors.js';import { assert, objectEquals, unreachable } from '../../common/util/util.js';
+import { Float16Array } from '../../external/petamoriken/float16/float16.js';
+
+
+import { kBit } from './constants.js';
+import {
+ cartesianProduct,
+ clamp,
+ correctlyRoundedF16,
+ isFiniteF16,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ isSubnormalNumberF64 } from
+'./math.js';
+
+/**
+ * Encodes a JS `number` into a "normalized" (unorm/snorm) integer representation with `bits` bits.
+ * Input must be between -1 and 1 if signed, or 0 and 1 if unsigned.
+ *
+ * MAINTENANCE_TODO: See if performance of texel_data improves if this function is pre-specialized
+ * for a particular `bits`/`signed`.
+ */
+export function floatAsNormalizedInteger(float, bits, signed) {
+ if (signed) {
+ assert(float >= -1 && float <= 1, () => `${float} out of bounds of snorm`);
+ const max = Math.pow(2, bits - 1) - 1;
+ return Math.round(float * max);
+ } else {
+ assert(float >= 0 && float <= 1, () => `${float} out of bounds of unorm`);
+ const max = Math.pow(2, bits) - 1;
+ return Math.round(float * max);
+ }
+}
+
+/**
+ * Decodes a JS `number` from a "normalized" (unorm/snorm) integer representation with `bits` bits.
+ * Input must be an integer in the range of the specified unorm/snorm type.
+ */
+export function normalizedIntegerAsFloat(integer, bits, signed) {
+ assert(Number.isInteger(integer));
+ if (signed) {
+ const max = Math.pow(2, bits - 1) - 1;
+ assert(integer >= -max - 1 && integer <= max);
+ if (integer === -max - 1) {
+ integer = -max;
+ }
+ return integer / max;
+ } else {
+ const max = Math.pow(2, bits) - 1;
+ assert(integer >= 0 && integer <= max);
+ return integer / max;
+ }
+}
+
+/**
+ * Compares 2 numbers. Returns true if their absolute value is
+ * less than or equal to maxDiff or if they are both NaN or the
+ * same sign infinity.
+ */
+export function numbersApproximatelyEqual(a, b, maxDiff = 0) {
+ return (
+ Number.isNaN(a) && Number.isNaN(b) ||
+ a === Number.POSITIVE_INFINITY && b === Number.POSITIVE_INFINITY ||
+ a === Number.NEGATIVE_INFINITY && b === Number.NEGATIVE_INFINITY ||
+ Math.abs(a - b) <= maxDiff);
+
+}
+
+/**
+ * Once-allocated ArrayBuffer/views to avoid overhead of allocation when converting between numeric formats
+ *
+ * workingData* is shared between multiple functions in this file, so to avoid re-entrancy problems, make sure in
+ * functions that use it that they don't call themselves or other functions that use workingData*.
+ */
+const workingData = new ArrayBuffer(8);
+const workingDataU32 = new Uint32Array(workingData);
+const workingDataU16 = new Uint16Array(workingData);
+const workingDataU8 = new Uint8Array(workingData);
+const workingDataF32 = new Float32Array(workingData);
+const workingDataF16 = new Float16Array(workingData);
+const workingDataI16 = new Int16Array(workingData);
+const workingDataI32 = new Int32Array(workingData);
+const workingDataI8 = new Int8Array(workingData);
+const workingDataF64 = new Float64Array(workingData);
+const workingDataView = new DataView(workingData);
+
+/**
+ * Encodes a JS `number` into an IEEE754 floating point number with the specified number of
+ * sign, exponent, mantissa bits, and exponent bias.
+ * Returns the result as an integer-valued JS `number`.
+ *
+ * Does not handle clamping, overflow, or denormal inputs.
+ * On underflow (result is subnormal), rounds to (signed) zero.
+ *
+ * MAINTENANCE_TODO: Replace usages of this with numberToFloatBits.
+ */
+export function float32ToFloatBits(
+n,
+signBits,
+exponentBits,
+mantissaBits,
+bias)
+{
+ assert(exponentBits <= 8);
+ assert(mantissaBits <= 23);
+
+ if (Number.isNaN(n)) {
+ // NaN = all exponent bits true, 1 or more mantissia bits true
+ return (1 << exponentBits) - 1 << mantissaBits | (1 << mantissaBits) - 1;
+ }
+
+ workingDataView.setFloat32(0, n, true);
+ const bits = workingDataView.getUint32(0, true);
+ // bits (32): seeeeeeeefffffffffffffffffffffff
+
+ // 0 or 1
+ const sign = bits >> 31 & signBits;
+
+ if (n === 0) {
+ if (sign === 1) {
+ // Handle negative zero.
+ return 1 << exponentBits + mantissaBits;
+ }
+ return 0;
+ }
+
+ if (signBits === 0) {
+ assert(n >= 0);
+ }
+
+ if (!Number.isFinite(n)) {
+ // Infinity = all exponent bits true, no mantissa bits true
+ // plus the sign bit.
+ return (
+ (1 << exponentBits) - 1 << mantissaBits | (n < 0 ? 2 ** (exponentBits + mantissaBits) : 0));
+
+ }
+
+ const mantissaBitsToDiscard = 23 - mantissaBits;
+
+ // >> to remove mantissa, & to remove sign, - 127 to remove bias.
+ const exp = (bits >> 23 & 0xff) - 127;
+
+ // Convert to the new biased exponent.
+ const newBiasedExp = bias + exp;
+ assert(newBiasedExp < 1 << exponentBits, () => `input number ${n} overflows target type`);
+
+ if (newBiasedExp <= 0) {
+ // Result is subnormal or zero. Round to (signed) zero.
+ return sign << exponentBits + mantissaBits;
+ } else {
+ // Mask only the mantissa, and discard the lower bits.
+ const newMantissa = (bits & 0x7fffff) >> mantissaBitsToDiscard;
+ return sign << exponentBits + mantissaBits | newBiasedExp << mantissaBits | newMantissa;
+ }
+}
+
+/**
+ * Encodes a JS `number` into an IEEE754 16 bit floating point number.
+ * Returns the result as an integer-valued JS `number`.
+ *
+ * Does not handle clamping, overflow, or denormal inputs.
+ * On underflow (result is subnormal), rounds to (signed) zero.
+ */
+export function float32ToFloat16Bits(n) {
+ return float32ToFloatBits(n, 1, 5, 10, 15);
+}
+
+/**
+ * Decodes an IEEE754 16 bit floating point number into a JS `number` and returns.
+ */
+export function float16BitsToFloat32(float16Bits) {
+ return floatBitsToNumber(float16Bits, kFloat16Format);
+}
+
+
+
+/** FloatFormat defining IEEE754 32-bit float. */
+export const kFloat32Format = { signed: 1, exponentBits: 8, mantissaBits: 23, bias: 127 };
+/** FloatFormat defining IEEE754 16-bit float. */
+export const kFloat16Format = { signed: 1, exponentBits: 5, mantissaBits: 10, bias: 15 };
+/** FloatFormat for 9 bit mantissa, 5 bit exponent unsigned float */
+export const kUFloat9e5Format = { signed: 0, exponentBits: 5, mantissaBits: 9, bias: 15 };
+
+/** Bitcast u32 (represented as integer Number) to f32 (represented as floating-point Number). */
+export function float32BitsToNumber(bits) {
+ workingDataU32[0] = bits;
+ return workingDataF32[0];
+}
+/** Bitcast f32 (represented as floating-point Number) to u32 (represented as integer Number). */
+export function numberToFloat32Bits(number) {
+ workingDataF32[0] = number;
+ return workingDataU32[0];
+}
+
+/**
+ * Decodes an IEEE754 float with the supplied format specification into a JS number.
+ *
+ * The format MUST be no larger than a 32-bit float.
+ */
+export function floatBitsToNumber(bits, fmt) {
+ // Pad the provided bits out to f32, then convert to a `number` with the wrong bias.
+ // E.g. for f16 to f32:
+ // - f16: S EEEEE MMMMMMMMMM
+ // ^ 000^^^^^ ^^^^^^^^^^0000000000000
+ // - f32: S eeeEEEEE MMMMMMMMMMmmmmmmmmmmmmm
+
+ const kNonSignBits = fmt.exponentBits + fmt.mantissaBits;
+ const kNonSignBitsMask = (1 << kNonSignBits) - 1;
+ const exponentAndMantissaBits = bits & kNonSignBitsMask;
+ const exponentMask = (1 << fmt.exponentBits) - 1 << fmt.mantissaBits;
+ const infinityOrNaN = (bits & exponentMask) === exponentMask;
+ if (infinityOrNaN) {
+ const mantissaMask = (1 << fmt.mantissaBits) - 1;
+ const signBit = 2 ** kNonSignBits;
+ const isNegative = (bits & signBit) !== 0;
+ return bits & mantissaMask ?
+ Number.NaN :
+ isNegative ?
+ Number.NEGATIVE_INFINITY :
+ Number.POSITIVE_INFINITY;
+ }
+ let f32BitsWithWrongBias =
+ exponentAndMantissaBits << kFloat32Format.mantissaBits - fmt.mantissaBits;
+ f32BitsWithWrongBias |= bits << 31 - kNonSignBits & 0x8000_0000;
+ const numberWithWrongBias = float32BitsToNumber(f32BitsWithWrongBias);
+ return numberWithWrongBias * 2 ** (kFloat32Format.bias - fmt.bias);
+}
+
+/**
+ * Convert ufloat9e5 bits from rgb9e5ufloat to a JS number
+ *
+ * The difference between `floatBitsToNumber` and `ufloatBitsToNumber`
+ * is that the latter doesn't use an implicit leading bit:
+ *
+ * floatBitsToNumber = 2^(exponent - bias) * (1 + mantissa / 2 ^ numMantissaBits)
+ * ufloatM9E5BitsToNumber = 2^(exponent - bias) * (mantissa / 2 ^ numMantissaBits)
+ * = 2^(exponent - bias - numMantissaBits) * mantissa
+ */
+export function ufloatM9E5BitsToNumber(bits, fmt) {
+ const exponent = bits >> fmt.mantissaBits;
+ const mantissaMask = (1 << fmt.mantissaBits) - 1;
+ const mantissa = bits & mantissaMask;
+ return mantissa * 2 ** (exponent - fmt.bias - fmt.mantissaBits);
+}
+
+/**
+ * Encodes a JS `number` into an IEEE754 floating point number with the specified format.
+ * Returns the result as an integer-valued JS `number`.
+ *
+ * Does not handle clamping, overflow, or denormal inputs.
+ * On underflow (result is subnormal), rounds to (signed) zero.
+ */
+export function numberToFloatBits(number, fmt) {
+ return float32ToFloatBits(number, fmt.signed, fmt.exponentBits, fmt.mantissaBits, fmt.bias);
+}
+
+/**
+ * Given a floating point number (as an integer representing its bits), computes how many ULPs it is
+ * from zero.
+ *
+ * Subnormal numbers are skipped, so that 0 is one ULP from the minimum normal number.
+ * Subnormal values are flushed to 0.
+ * Positive and negative 0 are both considered to be 0 ULPs from 0.
+ */
+export function floatBitsToNormalULPFromZero(bits, fmt) {
+ const mask_sign = fmt.signed << fmt.exponentBits + fmt.mantissaBits;
+ const mask_expt = (1 << fmt.exponentBits) - 1 << fmt.mantissaBits;
+ const mask_mant = (1 << fmt.mantissaBits) - 1;
+ const mask_rest = mask_expt | mask_mant;
+
+ assert(fmt.exponentBits + fmt.mantissaBits <= 31);
+
+ const sign = bits & mask_sign ? -1 : 1;
+ const rest = bits & mask_rest;
+ const subnormal_or_zero = (bits & mask_expt) === 0;
+ const infinity_or_nan = (bits & mask_expt) === mask_expt;
+ assert(!infinity_or_nan, 'no ulp representation for infinity/nan');
+
+ // The first normal number is mask_mant+1, so subtract mask_mant to make min_normal - zero = 1ULP.
+ const abs_ulp_from_zero = subnormal_or_zero ? 0 : rest - mask_mant;
+ return sign * abs_ulp_from_zero;
+}
+
+/**
+ * Encodes three JS `number` values into RGB9E5, returned as an integer-valued JS `number`.
+ *
+ * RGB9E5 represents three partial-precision floating-point numbers encoded into a single 32-bit
+ * value all sharing the same 5-bit exponent.
+ * There is no sign bit, and there is a shared 5-bit biased (15) exponent and a 9-bit
+ * mantissa for each channel. The mantissa does NOT have an implicit leading "1.",
+ * and instead has an implicit leading "0.".
+ *
+ * @see https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shared_exponent.txt
+ */
+export function packRGB9E5UFloat(r, g, b) {
+ const N = 9; // number of mantissa bits
+ const Emax = 31; // max exponent
+ const B = 15; // exponent bias
+ const sharedexp_max = ((1 << N) - 1) / (1 << N) * 2 ** (Emax - B);
+ const red_c = clamp(r, { min: 0, max: sharedexp_max });
+ const green_c = clamp(g, { min: 0, max: sharedexp_max });
+ const blue_c = clamp(b, { min: 0, max: sharedexp_max });
+ const max_c = Math.max(red_c, green_c, blue_c);
+ const exp_shared_p = Math.max(-B - 1, Math.floor(Math.log2(max_c))) + 1 + B;
+ const max_s = Math.floor(max_c / 2 ** (exp_shared_p - B - N) + 0.5);
+ const exp_shared = max_s === 1 << N ? exp_shared_p + 1 : exp_shared_p;
+ const scalar = 1 / 2 ** (exp_shared - B - N);
+ const red_s = Math.floor(red_c * scalar + 0.5);
+ const green_s = Math.floor(green_c * scalar + 0.5);
+ const blue_s = Math.floor(blue_c * scalar + 0.5);
+ assert(red_s >= 0 && red_s <= 0b111111111);
+ assert(green_s >= 0 && green_s <= 0b111111111);
+ assert(blue_s >= 0 && blue_s <= 0b111111111);
+ assert(exp_shared >= 0 && exp_shared <= 0b11111);
+ return (exp_shared << 27 | blue_s << 18 | green_s << 9 | red_s) >>> 0;
+}
+
+/**
+ * Decodes a RGB9E5 encoded color.
+ * @see packRGB9E5UFloat
+ */
+export function unpackRGB9E5UFloat(encoded) {
+ const N = 9; // number of mantissa bits
+ const B = 15; // exponent bias
+ const red_s = encoded >>> 0 & 0b111111111;
+ const green_s = encoded >>> 9 & 0b111111111;
+ const blue_s = encoded >>> 18 & 0b111111111;
+ const exp_shared = encoded >>> 27 & 0b11111;
+ const exp = Math.pow(2, exp_shared - B - N);
+ return {
+ R: exp * red_s,
+ G: exp * green_s,
+ B: exp * blue_s
+ };
+}
+
+/**
+ * Quantizes two f32s to f16 and then packs them in a u32
+ *
+ * This should implement the same behaviour as the builtin `pack2x16float` from
+ * WGSL.
+ *
+ * Caller is responsible to ensuring inputs are f32s
+ *
+ * @param x first f32 to be packed
+ * @param y second f32 to be packed
+ * @returns an array of possible results for pack2x16float. Elements are either
+ * a number or undefined.
+ * undefined indicates that any value is valid, since the input went
+ * out of bounds.
+ */
+export function pack2x16float(x, y) {
+ // Generates all possible valid u16 bit fields for a given f32 to f16 conversion.
+ // Assumes FTZ for both the f32 and f16 value is allowed.
+ const generateU16s = (n) => {
+ let contains_subnormals = isSubnormalNumberF32(n);
+ const n_f16s = correctlyRoundedF16(n);
+ contains_subnormals ||= n_f16s.some(isSubnormalNumberF16);
+
+ const n_u16s = n_f16s.map((f16) => {
+ workingDataF16[0] = f16;
+ return workingDataU16[0];
+ });
+
+ const contains_poszero = n_u16s.some((u) => u === kBit.f16.positive.zero);
+ const contains_negzero = n_u16s.some((u) => u === kBit.f16.negative.zero);
+ if (!contains_negzero && (contains_poszero || contains_subnormals)) {
+ n_u16s.push(kBit.f16.negative.zero);
+ }
+
+ if (!contains_poszero && (contains_negzero || contains_subnormals)) {
+ n_u16s.push(kBit.f16.positive.zero);
+ }
+
+ return n_u16s;
+ };
+
+ if (!isFiniteF16(x) || !isFiniteF16(y)) {
+ // This indicates any value is valid, so it isn't worth bothering
+ // calculating the more restrictive possibilities.
+ return [undefined];
+ }
+
+ const results = new Array();
+ for (const p of cartesianProduct(generateU16s(x), generateU16s(y))) {
+ assert(p.length === 2, 'cartesianProduct of 2 arrays returned an entry with not 2 elements');
+ workingDataU16[0] = p[0];
+ workingDataU16[1] = p[1];
+ results.push(workingDataU32[0]);
+ }
+
+ return results;
+}
+
+/**
+ * Converts two normalized f32s to i16s and then packs them in a u32
+ *
+ * This should implement the same behaviour as the builtin `pack2x16snorm` from
+ * WGSL.
+ *
+ * Caller is responsible to ensuring inputs are normalized f32s
+ *
+ * @param x first f32 to be packed
+ * @param y second f32 to be packed
+ * @returns a number that is expected result of pack2x16snorm.
+ */
+export function pack2x16snorm(x, y) {
+ // Converts f32 to i16 via the pack2x16snorm formula.
+ // FTZ is not explicitly handled, because all subnormals will produce a value
+ // between 0 and 1, but significantly away from the edges, so floor goes to 0.
+ const generateI16 = (n) => {
+ return Math.floor(0.5 + 32767 * Math.min(1, Math.max(-1, n)));
+ };
+
+ workingDataI16[0] = generateI16(x);
+ workingDataI16[1] = generateI16(y);
+
+ return workingDataU32[0];
+}
+
+/**
+ * Converts two normalized f32s to u16s and then packs them in a u32
+ *
+ * This should implement the same behaviour as the builtin `pack2x16unorm` from
+ * WGSL.
+ *
+ * Caller is responsible to ensuring inputs are normalized f32s
+ *
+ * @param x first f32 to be packed
+ * @param y second f32 to be packed
+ * @returns an number that is expected result of pack2x16unorm.
+ */
+export function pack2x16unorm(x, y) {
+ // Converts f32 to u16 via the pack2x16unorm formula.
+ // FTZ is not explicitly handled, because all subnormals will produce a value
+ // between 0.5 and much less than 1, so floor goes to 0.
+ const generateU16 = (n) => {
+ return Math.floor(0.5 + 65535 * Math.min(1, Math.max(0, n)));
+ };
+
+ workingDataU16[0] = generateU16(x);
+ workingDataU16[1] = generateU16(y);
+
+ return workingDataU32[0];
+}
+
+/**
+ * Converts four normalized f32s to i8s and then packs them in a u32
+ *
+ * This should implement the same behaviour as the builtin `pack4x8snorm` from
+ * WGSL.
+ *
+ * Caller is responsible to ensuring inputs are normalized f32s
+ *
+ * @param vals four f32s to be packed
+ * @returns a number that is expected result of pack4x8usorm.
+ */
+export function pack4x8snorm(...vals) {
+ // Converts f32 to u8 via the pack4x8snorm formula.
+ // FTZ is not explicitly handled, because all subnormals will produce a value
+ // between 0 and 1, so floor goes to 0.
+ const generateI8 = (n) => {
+ return Math.floor(0.5 + 127 * Math.min(1, Math.max(-1, n)));
+ };
+
+ for (const idx in vals) {
+ workingDataI8[idx] = generateI8(vals[idx]);
+ }
+
+ return workingDataU32[0];
+}
+
+/**
+ * Converts four normalized f32s to u8s and then packs them in a u32
+ *
+ * This should implement the same behaviour as the builtin `pack4x8unorm` from
+ * WGSL.
+ *
+ * Caller is responsible to ensuring inputs are normalized f32s
+ *
+ * @param vals four f32s to be packed
+ * @returns a number that is expected result of pack4x8unorm.
+ */
+export function pack4x8unorm(...vals) {
+ // Converts f32 to u8 via the pack4x8unorm formula.
+ // FTZ is not explicitly handled, because all subnormals will produce a value
+ // between 0.5 and much less than 1, so floor goes to 0.
+ const generateU8 = (n) => {
+ return Math.floor(0.5 + 255 * Math.min(1, Math.max(0, n)));
+ };
+
+ for (const idx in vals) {
+ workingDataU8[idx] = generateU8(vals[idx]);
+ }
+
+ return workingDataU32[0];
+}
+
+/**
+ * Asserts that a number is within the representable (inclusive) of the integer type with the
+ * specified number of bits and signedness.
+ *
+ * MAINTENANCE_TODO: Assert isInteger? Then this function "asserts that a number is representable"
+ * by the type.
+ */
+export function assertInIntegerRange(n, bits, signed) {
+ if (signed) {
+ const min = -Math.pow(2, bits - 1);
+ const max = Math.pow(2, bits - 1) - 1;
+ assert(n >= min && n <= max);
+ } else {
+ const max = Math.pow(2, bits) - 1;
+ assert(n >= 0 && n <= max);
+ }
+}
+
+/**
+ * Converts a linear value into a "gamma"-encoded value using the sRGB-clamped transfer function.
+ */
+export function gammaCompress(n) {
+ n = n <= 0.0031308 ? 323 * n / 25 : (211 * Math.pow(n, 5 / 12) - 11) / 200;
+ return clamp(n, { min: 0, max: 1 });
+}
+
+/**
+ * Converts a "gamma"-encoded value into a linear value using the sRGB-clamped transfer function.
+ */
+export function gammaDecompress(n) {
+ n = n <= 0.04045 ? n * 25 / 323 : Math.pow((200 * n + 11) / 211, 12 / 5);
+ return clamp(n, { min: 0, max: 1 });
+}
+
+/** Converts a 32-bit float value to a 32-bit unsigned integer value */
+export function float32ToUint32(f32) {
+ workingDataF32[0] = f32;
+ return workingDataU32[0];
+}
+
+/** Converts a 32-bit unsigned integer value to a 32-bit float value */
+export function uint32ToFloat32(u32) {
+ workingDataU32[0] = u32;
+ return workingDataF32[0];
+}
+
+/** Converts a 32-bit float value to a 32-bit signed integer value */
+export function float32ToInt32(f32) {
+ workingDataF32[0] = f32;
+ return workingDataI32[0];
+}
+
+/** Converts a 32-bit unsigned integer value to a 32-bit signed integer value */
+export function uint32ToInt32(u32) {
+ workingDataU32[0] = u32;
+ return workingDataI32[0];
+}
+
+/** Converts a 16-bit float value to a 16-bit unsigned integer value */
+export function float16ToUint16(f16) {
+ workingDataF16[0] = f16;
+ return workingDataU16[0];
+}
+
+/** Converts a 16-bit unsigned integer value to a 16-bit float value */
+export function uint16ToFloat16(u16) {
+ workingDataU16[0] = u16;
+ return workingDataF16[0];
+}
+
+/** Converts a 16-bit float value to a 16-bit signed integer value */
+export function float16ToInt16(f16) {
+ workingDataF16[0] = f16;
+ return workingDataI16[0];
+}
+
+/** A type of number representable by Scalar. */
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** ScalarType describes the type of WGSL Scalar. */
+export class ScalarType {
+ // The named type
+ // In bytes
+ // reads a scalar from a buffer
+
+ constructor(kind, size, read) {
+ this.kind = kind;
+ this._size = size;
+ this.read = read;
+ }
+
+ toString() {
+ return this.kind;
+ }
+
+ get size() {
+ return this._size;
+ }
+
+ /** Constructs a Scalar of this type with `value` */
+ create(value) {
+ 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);
+ }
+ }
+}
+
+/** VectorType describes the type of WGSL Vector. */
+export class VectorType {
+ // Number of elements in the vector
+ // Element type
+
+ constructor(width, elementType) {
+ this.width = width;
+ this.elementType = elementType;
+ }
+
+ /**
+ * @returns a vector constructed from the values read from the buffer at the
+ * given byte offset
+ */
+ read(buf, offset) {
+ const elements = [];
+ for (let i = 0; i < this.width; i++) {
+ elements[i] = this.elementType.read(buf, offset);
+ offset += this.elementType.size;
+ }
+ return new Vector(elements);
+ }
+
+ toString() {
+ return `vec${this.width}<${this.elementType}>`;
+ }
+
+ get size() {
+ return this.elementType.size * this.width;
+ }
+
+ /** Constructs a Vector of this type with the given values */
+ create(value) {
+ 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)));
+ }
+}
+
+// Maps a string representation of a vector type to vector type.
+const vectorTypes = new Map();
+
+export function TypeVec(width, elementType) {
+ const key = `${elementType.toString()} ${width}}`;
+ let ty = vectorTypes.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new VectorType(width, elementType);
+ vectorTypes.set(key, ty);
+ return ty;
+}
+
+/** MatrixType describes the type of WGSL Matrix. */
+export class MatrixType {
+ // Number of columns in the Matrix
+ // Number of elements per column in the Matrix
+ // Element type
+
+ constructor(cols, rows, elementType) {
+ this.cols = cols;
+ this.rows = rows;
+ assert(
+ elementType.kind === 'f32' ||
+ elementType.kind === 'f16' ||
+ elementType.kind === 'abstract-float',
+ "MatrixType can only have elementType of 'f32' or 'f16' or 'abstract-float'"
+ );
+ this.elementType = elementType;
+ }
+
+ /**
+ * @returns a Matrix constructed from the values read from the buffer at the
+ * given byte offset
+ */
+ read(buf, offset) {
+ const elements = [...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);
+ offset += this.elementType.size;
+ }
+
+ // vec3 have one padding element, so need to skip in matrices
+ if (this.rows === 3) {
+ offset += this.elementType.size;
+ }
+ }
+ return new Matrix(elements);
+ }
+
+ toString() {
+ return `mat${this.cols}x${this.rows}<${this.elementType}>`;
+ }
+}
+
+// Maps a string representation of a Matrix type to Matrix type.
+const matrixTypes = new Map();
+
+export function TypeMat(cols, rows, elementType) {
+ const key = `${elementType.toString()} ${cols} ${rows}`;
+ let ty = matrixTypes.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new MatrixType(cols, rows, elementType);
+ matrixTypes.set(key, ty);
+ return ty;
+}
+
+/** Type is a ScalarType, VectorType, or MatrixType. */
+
+
+/** Copy bytes from `buf` at `offset` into the working data, then read it out using `workingDataOut` */
+function valueFromBytes(workingDataOut, buf, offset) {
+ for (let i = 0; i < workingDataOut.BYTES_PER_ELEMENT; ++i) {
+ workingDataU8[i] = buf[offset + i];
+ }
+ return workingDataOut[0];
+}
+
+export const TypeI32 = new ScalarType('i32', 4, (buf, offset) =>
+i32(valueFromBytes(workingDataI32, buf, offset))
+);
+export const TypeU32 = new ScalarType('u32', 4, (buf, offset) =>
+u32(valueFromBytes(workingDataU32, buf, offset))
+);
+export const TypeAbstractFloat = new ScalarType(
+ 'abstract-float',
+ 8,
+ (buf, offset) => abstractFloat(valueFromBytes(workingDataF64, buf, offset))
+);
+export const TypeF64 = new ScalarType('f64', 8, (buf, offset) =>
+f64(valueFromBytes(workingDataF64, buf, offset))
+);
+export const TypeF32 = new ScalarType('f32', 4, (buf, offset) =>
+f32(valueFromBytes(workingDataF32, buf, offset))
+);
+export const TypeI16 = new ScalarType('i16', 2, (buf, offset) =>
+i16(valueFromBytes(workingDataI16, buf, offset))
+);
+export const TypeU16 = new ScalarType('u16', 2, (buf, offset) =>
+u16(valueFromBytes(workingDataU16, buf, offset))
+);
+export const TypeF16 = new ScalarType('f16', 2, (buf, offset) =>
+f16Bits(valueFromBytes(workingDataU16, buf, offset))
+);
+export const TypeI8 = new ScalarType('i8', 1, (buf, offset) =>
+i8(valueFromBytes(workingDataI8, buf, offset))
+);
+export const TypeU8 = new ScalarType('u8', 1, (buf, offset) =>
+u8(valueFromBytes(workingDataU8, buf, offset))
+);
+export const TypeBool = new ScalarType('bool', 4, (buf, offset) =>
+bool(valueFromBytes(workingDataU32, buf, offset) !== 0)
+);
+
+/** @returns the ScalarType from the ScalarKind */
+export function scalarType(kind) {
+ switch (kind) {
+ case 'abstract-float':
+ return TypeAbstractFloat;
+ case 'f64':
+ return TypeF64;
+ case 'f32':
+ return TypeF32;
+ case 'f16':
+ return TypeF16;
+ case 'u32':
+ return TypeU32;
+ case 'u16':
+ return TypeU16;
+ case 'u8':
+ return TypeU8;
+ case 'i32':
+ return TypeI32;
+ case 'i16':
+ return TypeI16;
+ case 'i8':
+ return TypeI8;
+ case 'bool':
+ return TypeBool;
+ }
+}
+
+/** @returns the number of scalar (element) types of the given Type */
+export function numElementsOf(ty) {
+ if (ty instanceof ScalarType) {
+ return 1;
+ }
+ if (ty instanceof VectorType) {
+ return ty.width;
+ }
+ if (ty instanceof MatrixType) {
+ return ty.cols * ty.rows;
+ }
+ throw new Error(`unhandled type ${ty}`);
+}
+
+/** @returns the scalar elements of the given Value */
+export function elementsOf(value) {
+ if (value instanceof Scalar) {
+ return [value];
+ }
+ if (value instanceof Vector) {
+ return value.elements;
+ }
+ if (value instanceof Matrix) {
+ return value.elements.flat();
+ }
+ throw new Error(`unhandled value ${value}`);
+}
+
+/** @returns the scalar (element) type of the given Type */
+export function scalarTypeOf(ty) {
+ if (ty instanceof ScalarType) {
+ return ty;
+ }
+ if (ty instanceof VectorType) {
+ return ty.elementType;
+ }
+ if (ty instanceof MatrixType) {
+ return ty.elementType;
+ }
+ throw new Error(`unhandled type ${ty}`);
+}
+
+/** ScalarValue is the JS type that can be held by a Scalar */
+
+
+/** Class that encapsulates a single scalar value of various types. */
+export class Scalar {
+ // The scalar value
+ // The type of the scalar
+
+ // The scalar value, packed in one or two 32-bit unsigned integers.
+ // Whether or not the bits1 is used depends on `this.type.size`.
+
+
+
+ constructor(type, value, bits1, bits0) {
+ this.value = value;
+ this.type = type;
+ this.bits1 = bits1;
+ this.bits0 = bits0;
+ }
+
+ /**
+ * 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`
+ */
+ copyTo(buffer, offset) {
+ 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++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /**
+ * @returns the WGSL representation of this scalar value
+ */
+ wgsl() {
+ const withPoint = (x) => {
+ const str = `${x}`;
+ return str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ };
+ if (isFinite(this.value)) {
+ switch (this.type.kind) {
+ case 'abstract-float':
+ return `${withPoint(this.value)}`;
+ case 'f64':
+ return `${withPoint(this.value)}`;
+ case 'f32':
+ return `${withPoint(this.value)}f`;
+ case 'f16':
+ return `${withPoint(this.value)}h`;
+ case 'u32':
+ return `${this.value}u`;
+ case 'i32':
+ return `i32(${this.value})`;
+ case 'bool':
+ return `${this.value}`;
+ }
+ }
+ throw new Error(
+ `scalar of value ${this.value} and type ${this.type} has no WGSL representation`
+ );
+ }
+
+ toString() {
+ if (this.type.kind === 'bool') {
+ return Colors.bold(this.value.toString());
+ }
+ 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;
+ 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})`;
+ }
+ }
+ }
+}
+
+
+
+
+
+/** 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,
+workingDataArray,
+value)
+{
+ // 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,
+workingDataStoreArray,
+workingDataLoadArray,
+bits)
+{
+ // 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]);
+}
+
+/** Create an AbstractFloat from a numeric value, a JS `number`. */
+export const abstractFloat = (value) =>
+scalarFromValue(TypeAbstractFloat, workingDataF64, value);
+
+/** Create an f64 from a numeric value, a JS `number`. */
+export const f64 = (value) => scalarFromValue(TypeF64, workingDataF64, value);
+
+/** Create an f32 from a numeric value, a JS `number`. */
+export const f32 = (value) => scalarFromValue(TypeF32, workingDataF32, value);
+
+/** Create an f16 from a numeric value, a JS `number`. */
+export const f16 = (value) => scalarFromValue(TypeF16, workingDataF16, value);
+
+/** Create an f32 from a bit representation, a uint32 represented as a JS `number`. */
+export const f32Bits = (bits) =>
+scalarFromBits(TypeF32, workingDataU32, workingDataF32, bits);
+
+/** Create an f16 from a bit representation, a uint16 represented as a JS `number`. */
+export const f16Bits = (bits) =>
+scalarFromBits(TypeF16, workingDataU16, workingDataF16, bits);
+
+/** Create an i32 from a numeric value, a JS `number`. */
+export const i32 = (value) => scalarFromValue(TypeI32, workingDataI32, value);
+
+/** Create an i16 from a numeric value, a JS `number`. */
+export const i16 = (value) => scalarFromValue(TypeI16, workingDataI16, value);
+
+/** Create an i8 from a numeric value, a JS `number`. */
+export const i8 = (value) => scalarFromValue(TypeI8, workingDataI8, value);
+
+/** Create an i32 from a bit representation, a uint32 represented as a JS `number`. */
+export const i32Bits = (bits) =>
+scalarFromBits(TypeI32, workingDataU32, workingDataI32, bits);
+
+/** Create an i16 from a bit representation, a uint16 represented as a JS `number`. */
+export const i16Bits = (bits) =>
+scalarFromBits(TypeI16, workingDataU16, workingDataI16, bits);
+
+/** Create an i8 from a bit representation, a uint8 represented as a JS `number`. */
+export const i8Bits = (bits) =>
+scalarFromBits(TypeI8, workingDataU8, workingDataI8, bits);
+
+/** Create a u32 from a numeric value, a JS `number`. */
+export const u32 = (value) => scalarFromValue(TypeU32, workingDataU32, value);
+
+/** Create a u16 from a numeric value, a JS `number`. */
+export const u16 = (value) => scalarFromValue(TypeU16, workingDataU16, value);
+
+/** Create a u8 from a numeric value, a JS `number`. */
+export const u8 = (value) => scalarFromValue(TypeU8, workingDataU8, value);
+
+/** Create an u32 from a bit representation, a uint32 represented as a JS `number`. */
+export const u32Bits = (bits) =>
+scalarFromBits(TypeU32, workingDataU32, workingDataU32, bits);
+
+/** Create an u16 from a bit representation, a uint16 represented as a JS `number`. */
+export const u16Bits = (bits) =>
+scalarFromBits(TypeU16, workingDataU16, workingDataU16, bits);
+
+/** Create an u8 from a bit representation, a uint8 represented as a JS `number`. */
+export const u8Bits = (bits) =>
+scalarFromBits(TypeU8, workingDataU8, workingDataU8, bits);
+
+/** Create a boolean value. */
+export function bool(value) {
+ // 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]);
+}
+
+/** A 'true' literal value */
+export const True = bool(true);
+
+/** A 'false' literal value */
+export const False = bool(false);
+
+/**
+ * Class that encapsulates a vector value.
+ */
+export class Vector {
+
+
+
+ constructor(elements) {
+ if (elements.length < 2 || elements.length > 4) {
+ throw new Error(`vector element count must be between 2 and 4, got ${elements.length}`);
+ }
+ for (let i = 1; i < elements.length; i++) {
+ const a = elements[0].type;
+ const b = elements[i].type;
+ if (a !== b) {
+ throw new Error(
+ `cannot mix vector element types. Found elements with types '${a}' and '${b}'`
+ );
+ }
+ }
+ this.elements = elements;
+ this.type = TypeVec(elements.length, elements[0].type);
+ }
+
+ /**
+ * Copies the vector value to the Uint8Array buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the byte offset within buffer
+ */
+ copyTo(buffer, offset) {
+ for (const element of this.elements) {
+ element.copyTo(buffer, offset);
+ offset += this.type.elementType.size;
+ }
+ }
+
+ /**
+ * @returns the WGSL representation of this vector value
+ */
+ wgsl() {
+ const els = this.elements.map((v) => v.wgsl()).join(', ');
+ return `vec${this.type.width}(${els})`;
+ }
+
+ toString() {
+ return `${this.type}(${this.elements.map((e) => e.toString()).join(', ')})`;
+ }
+
+ get x() {
+ assert(0 < this.elements.length);
+ return this.elements[0];
+ }
+
+ get y() {
+ assert(1 < this.elements.length);
+ return this.elements[1];
+ }
+
+ get z() {
+ assert(2 < this.elements.length);
+ return this.elements[2];
+ }
+
+ get w() {
+ assert(3 < this.elements.length);
+ return this.elements[3];
+ }
+}
+
+/** Helper for constructing a new two-element vector with the provided values */
+export function vec2(x, y) {
+ return new Vector([x, y]);
+}
+
+/** Helper for constructing a new three-element vector with the provided values */
+export function vec3(x, y, z) {
+ return new Vector([x, y, z]);
+}
+
+/** Helper for constructing a new four-element vector with the provided values */
+export function vec4(x, y, z, w) {
+ return new Vector([x, y, z, w]);
+}
+
+/**
+ * Helper for constructing Vectors from arrays of numbers
+ *
+ * @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, op) {
+ switch (v.length) {
+ case 2:
+ return vec2(op(v[0]), op(v[1]));
+ case 3:
+ return vec3(op(v[0]), op(v[1]), op(v[2]));
+ case 4:
+ return vec4(op(v[0]), op(v[1]), op(v[2]), op(v[3]));
+ }
+ unreachable(`input to 'toVector' must contain 2, 3, or 4 elements`);
+}
+
+/**
+ * Class that encapsulates a Matrix value.
+ */
+export class Matrix {
+
+
+
+ constructor(elements) {
+ 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}`);
+ }
+
+ const num_rows = elements[0].length;
+ if (!elements.every((c) => c.length === num_rows)) {
+ throw new Error(`cannot mix matrix column lengths`);
+ }
+
+ if (num_rows < 2 || num_rows > 4) {
+ throw new Error(`matrix rows count must be between 2 and 4, got ${num_rows}`);
+ }
+
+ const elem_type = elements[0][0].type;
+ if (!elements.every((c) => c.every((r) => objectEquals(r.type, elem_type)))) {
+ throw new Error(`cannot mix matrix element types`);
+ }
+
+ this.elements = elements;
+ this.type = TypeMat(num_cols, num_rows, elem_type);
+ }
+
+ /**
+ * Copies the matrix value to the Uint8Array buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the byte offset within buffer
+ */
+ copyTo(buffer, offset) {
+ for (let i = 0; i < this.type.cols; i++) {
+ for (let j = 0; j < this.type.rows; j++) {
+ this.elements[i][j].copyTo(buffer, offset);
+ offset += this.type.elementType.size;
+ }
+
+ // vec3 have one padding element, so need to skip in matrices
+ if (this.type.rows === 3) {
+ offset += this.type.elementType.size;
+ }
+ }
+ }
+
+ /**
+ * @returns the WGSL representation of this matrix value
+ */
+ wgsl() {
+ const els = this.elements.flatMap((c) => c.map((r) => r.wgsl())).join(', ');
+ return `mat${this.type.cols}x${this.type.rows}(${els})`;
+ }
+
+ toString() {
+ return `${this.type}(${this.elements.map((c) => c.join(', ')).join(', ')})`;
+ }
+}
+
+/**
+ * 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, op) {
+ const cols = m.length;
+ const rows = m[0].length;
+ const elements = [...Array(cols)].map((_) => [...Array(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);
+}
+
+/** Value is a Scalar or Vector value. */var
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+SerializedScalarKind = /*#__PURE__*/function (SerializedScalarKind) {SerializedScalarKind[SerializedScalarKind["AbstractFloat"] = 0] = "AbstractFloat";SerializedScalarKind[SerializedScalarKind["F64"] = 1] = "F64";SerializedScalarKind[SerializedScalarKind["F32"] = 2] = "F32";SerializedScalarKind[SerializedScalarKind["F16"] = 3] = "F16";SerializedScalarKind[SerializedScalarKind["U32"] = 4] = "U32";SerializedScalarKind[SerializedScalarKind["U16"] = 5] = "U16";SerializedScalarKind[SerializedScalarKind["U8"] = 6] = "U8";SerializedScalarKind[SerializedScalarKind["I32"] = 7] = "I32";SerializedScalarKind[SerializedScalarKind["I16"] = 8] = "I16";SerializedScalarKind[SerializedScalarKind["I8"] = 9] = "I8";SerializedScalarKind[SerializedScalarKind["Bool"] = 10] = "Bool";return SerializedScalarKind;}(SerializedScalarKind || {});
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** serializeScalarKind() serializes a ScalarKind to a BinaryStream */
+function serializeScalarKind(s, v) {
+ switch (v) {
+ case 'abstract-float':
+ s.writeU8(SerializedScalarKind.AbstractFloat);
+ return;
+ case 'f64':
+ s.writeU8(SerializedScalarKind.F64);
+ return;
+ case 'f32':
+ s.writeU8(SerializedScalarKind.F32);
+ return;
+ case 'f16':
+ s.writeU8(SerializedScalarKind.F16);
+ return;
+ case 'u32':
+ s.writeU8(SerializedScalarKind.U32);
+ return;
+ case 'u16':
+ s.writeU8(SerializedScalarKind.U16);
+ return;
+ case 'u8':
+ s.writeU8(SerializedScalarKind.U8);
+ return;
+ case 'i32':
+ s.writeU8(SerializedScalarKind.I32);
+ return;
+ case 'i16':
+ s.writeU8(SerializedScalarKind.I16);
+ return;
+ case 'i8':
+ s.writeU8(SerializedScalarKind.I8);
+ return;
+ case 'bool':
+ s.writeU8(SerializedScalarKind.Bool);
+ return;
+ }
+}
+
+/** deserializeScalarKind() deserializes a ScalarKind from a BinaryStream */
+function deserializeScalarKind(s) {
+ const kind = s.readU8();
+ switch (kind) {
+ case SerializedScalarKind.AbstractFloat:
+ return 'abstract-float';
+ case SerializedScalarKind.F64:
+ return 'f64';
+ case SerializedScalarKind.F32:
+ return 'f32';
+ case SerializedScalarKind.F16:
+ return 'f16';
+ case SerializedScalarKind.U32:
+ return 'u32';
+ case SerializedScalarKind.U16:
+ return 'u16';
+ case SerializedScalarKind.U8:
+ return 'u8';
+ case SerializedScalarKind.I32:
+ return 'i32';
+ case SerializedScalarKind.I16:
+ return 'i16';
+ case SerializedScalarKind.I8:
+ return 'i8';
+ case SerializedScalarKind.Bool:
+ return 'bool';
+ default:
+ unreachable(`invalid serialized ScalarKind: ${kind}`);
+ }
+}var
+
+SerializedValueKind = /*#__PURE__*/function (SerializedValueKind) {SerializedValueKind[SerializedValueKind["Scalar"] = 0] = "Scalar";SerializedValueKind[SerializedValueKind["Vector"] = 1] = "Vector";SerializedValueKind[SerializedValueKind["Matrix"] = 2] = "Matrix";return SerializedValueKind;}(SerializedValueKind || {});
+
+
+
+
+
+/** serializeValue() serializes a Value to a BinaryStream */
+export function serializeValue(s, v) {
+ const serializeScalar = (scalar, kind) => {
+ 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;
+ case 'bool':
+ s.writeBool(scalar.value);
+ return;
+ }
+ };
+
+ if (v instanceof Scalar) {
+ s.writeU8(SerializedValueKind.Scalar);
+ serializeScalarKind(s, v.type.kind);
+ serializeScalar(v, v.type.kind);
+ return;
+ }
+ if (v instanceof Vector) {
+ s.writeU8(SerializedValueKind.Vector);
+ serializeScalarKind(s, v.type.elementType.kind);
+ s.writeU8(v.type.width);
+ for (const element of v.elements) {
+ serializeScalar(element, v.type.elementType.kind);
+ }
+ return;
+ }
+ if (v instanceof Matrix) {
+ s.writeU8(SerializedValueKind.Matrix);
+ serializeScalarKind(s, v.type.elementType.kind);
+ s.writeU8(v.type.cols);
+ s.writeU8(v.type.rows);
+ for (const column of v.elements) {
+ for (const element of column) {
+ serializeScalar(element, v.type.elementType.kind);
+ }
+ }
+ return;
+ }
+
+ unreachable(`unhandled value type: ${v}`);
+}
+
+/** deserializeValue() deserializes a Value from a BinaryStream */
+export function deserializeValue(s) {
+ const deserializeScalar = (kind) => {
+ switch (kind) {
+ case 'abstract-float':
+ return abstractFloat(s.readF64());
+ case 'f64':
+ return f64(s.readF64());
+ case 'f32':
+ return f32(s.readF32());
+ case 'f16':
+ return f16(s.readF16());
+ case 'u32':
+ return u32(s.readU32());
+ case 'u16':
+ return u16(s.readU16());
+ case 'u8':
+ return u8(s.readU8());
+ case 'i32':
+ return i32(s.readI32());
+ case 'i16':
+ return i16(s.readI16());
+ case 'i8':
+ return i8(s.readI8());
+ case 'bool':
+ return bool(s.readBool());
+ }
+ };
+ const valueKind = s.readU8();
+ const scalarKind = deserializeScalarKind(s);
+ switch (valueKind) {
+ case SerializedValueKind.Scalar:
+ return deserializeScalar(scalarKind);
+ case SerializedValueKind.Vector:{
+ const width = s.readU8();
+ const scalars = new Array(width);
+ for (let i = 0; i < width; i++) {
+ scalars[i] = deserializeScalar(scalarKind);
+ }
+ return new Vector(scalars);
+ }
+ case SerializedValueKind.Matrix:{
+ const numCols = s.readU8();
+ const numRows = s.readU8();
+ const columns = new Array(numCols);
+ for (let c = 0; c < numCols; c++) {
+ columns[c] = new Array(numRows);
+ for (let i = 0; i < numRows; i++) {
+ columns[c][i] = deserializeScalar(scalarKind);
+ }
+ }
+ return new Matrix(columns);
+ }
+ default:
+ unreachable(`invalid serialized value kind: ${valueKind}`);
+ }
+}
+
+/** @returns if the Value is a float scalar type */
+export function isFloatValue(v) {
+ return isFloatType(v.type);
+}
+
+/**
+ * @returns if `ty` is an abstract numeric type.
+ * @note this does not consider composite types.
+ * Use elementType() if you want to test the element type.
+ */
+export function isAbstractType(ty) {
+ if (ty instanceof ScalarType) {
+ return ty.kind === 'abstract-float';
+ }
+ return false;
+}
+
+/**
+ * @returns if `ty` is a floating point type.
+ * @note this does not consider composite types.
+ * Use elementType() if you want to test the element type.
+ */
+export function isFloatType(ty) {
+ if (ty instanceof ScalarType) {
+ return (
+ ty.kind === 'abstract-float' || ty.kind === 'f64' || ty.kind === 'f32' || ty.kind === 'f16');
+
+ }
+ return false;
+}
+
+/// All floating-point scalar types
+export const kAllFloatScalars = [TypeAbstractFloat, TypeF32, TypeF16];
+
+/// All floating-point vec2 types
+export const kAllFloatVector2 = [
+TypeVec(2, TypeAbstractFloat),
+TypeVec(2, TypeF32),
+TypeVec(2, TypeF16)];
+
+
+/// All floating-point vec3 types
+export const kAllFloatVector3 = [
+TypeVec(3, TypeAbstractFloat),
+TypeVec(3, TypeF32),
+TypeVec(3, TypeF16)];
+
+
+/// All floating-point vec4 types
+export const kAllFloatVector4 = [
+TypeVec(4, TypeAbstractFloat),
+TypeVec(4, TypeF32),
+TypeVec(4, TypeF16)];
+
+
+/// All floating-point vector types
+export const kAllFloatVectors = [
+...kAllFloatVector2,
+...kAllFloatVector3,
+...kAllFloatVector4];
+
+
+/// All floating-point scalar and vector types
+export const kAllFloatScalarsAndVectors = [...kAllFloatScalars, ...kAllFloatVectors];
+
+/// 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)];
+
+
+/// All signed integer scalar and vector types
+export const kAllSignedIntegerScalarsAndVectors = [
+TypeI32,
+TypeVec(2, TypeI32),
+TypeVec(3, TypeI32),
+TypeVec(4, TypeI32)];
+
+
+/// All unsigned integer scalar and vector types
+export const kAllUnsignedIntegerScalarsAndVectors = [
+TypeU32,
+TypeVec(2, TypeU32),
+TypeVec(3, TypeU32),
+TypeVec(4, TypeU32)];
+
+
+/// All floating-point and integer scalar and vector types
+export const kAllFloatAndIntegerScalarsAndVectors = [
+...kAllFloatScalarsAndVectors,
+...kAllIntegerScalarsAndVectors];
+
+
+/// All floating-point and signed integer scalar and vector types
+export const kAllFloatAndSignedIntegerScalarsAndVectors = [
+...kAllFloatScalarsAndVectors,
+...kAllSignedIntegerScalarsAndVectors];
+
+
+/** @returns the inner element type of the given type */
+export function elementType(t) {
+ if (t instanceof ScalarType) {
+ return t;
+ }
+ return t.elementType;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/copy_to_texture.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/copy_to_texture.js
new file mode 100644
index 0000000000..beaad649fe
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/copy_to_texture.js
@@ -0,0 +1,192 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, memcpy } from '../../common/util/util.js';import { GPUTest, TextureTestMixin } from '../gpu_test.js';
+import { reifyExtent3D, reifyOrigin3D } from '../util/unions.js';
+
+import { makeInPlaceColorConversion } from './color_space_conversion.js';
+import { TexelView } from './texture/texel_view.js';
+
+
+/**
+ * Predefined copy sub rect meta infos.
+ */
+export const kCopySubrectInfo = [
+{
+ srcOrigin: { x: 2, y: 2 },
+ dstOrigin: { x: 0, y: 0, z: 0 },
+ srcSize: { width: 16, height: 16 },
+ dstSize: { width: 4, height: 4 },
+ copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }
+},
+{
+ srcOrigin: { x: 10, y: 2 },
+ dstOrigin: { x: 0, y: 0, z: 0 },
+ srcSize: { width: 16, height: 16 },
+ dstSize: { width: 4, height: 4 },
+ copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }
+},
+{
+ srcOrigin: { x: 2, y: 10 },
+ dstOrigin: { x: 0, y: 0, z: 0 },
+ srcSize: { width: 16, height: 16 },
+ dstSize: { width: 4, height: 4 },
+ copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }
+},
+{
+ srcOrigin: { x: 10, y: 10 },
+ dstOrigin: { x: 0, y: 0, z: 0 },
+ srcSize: { width: 16, height: 16 },
+ dstSize: { width: 4, height: 4 },
+ copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }
+},
+{
+ srcOrigin: { x: 2, y: 2 },
+ dstOrigin: { x: 2, y: 2, z: 0 },
+ srcSize: { width: 16, height: 16 },
+ dstSize: { width: 16, height: 16 },
+ copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }
+},
+{
+ srcOrigin: { x: 10, y: 2 },
+ dstOrigin: { x: 2, y: 2, z: 0 },
+ srcSize: { width: 16, height: 16 },
+ dstSize: { width: 16, height: 16 },
+ copyExtent: { width: 4, height: 4, depthOrArrayLayers: 1 }
+}];
+
+
+export class CopyToTextureUtils extends TextureTestMixin(GPUTest) {
+ doFlipY(
+ sourcePixels,
+ width,
+ height,
+ bytesPerPixel)
+ {
+ const dstPixels = new Uint8ClampedArray(width * height * bytesPerPixel);
+ for (let i = 0; i < height; ++i) {
+ for (let j = 0; j < width; ++j) {
+ const srcPixelPos = i * width + j;
+ // WebGL readPixel returns pixels from bottom-left origin. Using CopyExternalImageToTexture
+ // to copy from WebGL Canvas keeps top-left origin. So the expectation from webgl.readPixel should
+ // be flipped.
+ const dstPixelPos = (height - i - 1) * width + j;
+
+ memcpy(
+ { src: sourcePixels, start: srcPixelPos * bytesPerPixel, length: bytesPerPixel },
+ { dst: dstPixels, start: dstPixelPos * bytesPerPixel }
+ );
+ }
+ }
+
+ return dstPixels;
+ }
+
+ getExpectedDstPixelsFromSrcPixels({
+ srcPixels,
+ srcOrigin,
+ srcSize,
+ dstOrigin,
+ dstSize,
+ subRectSize,
+ format,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy,
+ conversion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }) {
+ const applyConversion = makeInPlaceColorConversion(conversion);
+
+ const reifySrcOrigin = reifyOrigin3D(srcOrigin);
+ const reifySrcSize = reifyExtent3D(srcSize);
+ const reifyDstOrigin = reifyOrigin3D(dstOrigin);
+ const reifyDstSize = reifyExtent3D(dstSize);
+ const reifySubRectSize = reifyExtent3D(subRectSize);
+
+ assert(
+ reifyDstOrigin.x + reifySubRectSize.width <= reifyDstSize.width &&
+ reifyDstOrigin.y + reifySubRectSize.height <= reifyDstSize.height,
+ 'subrect is out of bounds'
+ );
+
+ const divide = 255.0;
+ return TexelView.fromTexelsAsColors(
+ format,
+ (coords) => {
+ assert(
+ coords.x >= reifyDstOrigin.x &&
+ coords.y >= reifyDstOrigin.y &&
+ coords.x < reifyDstOrigin.x + reifySubRectSize.width &&
+ coords.y < reifyDstOrigin.y + reifySubRectSize.height &&
+ coords.z === 0,
+ 'out of bounds'
+ );
+ // Map dst coords to get candidate src pixel position in y.
+ let yInSubRect = coords.y - reifyDstOrigin.y;
+
+ // If srcDoFlipYDuringCopy is true, a flipY op has been applied to src during copy.
+ // WebGPU spec requires origin option relative to the top-left corner of the source image,
+ // increasing downward consistently.
+ // https://www.w3.org/TR/webgpu/#dom-gpuimagecopyexternalimage-flipy
+ // Flip only happens in copy rect contents and src origin always top-left.
+ // Get candidate src pixel position in y by mirroring in copy sub rect.
+ if (srcDoFlipYDuringCopy) yInSubRect = reifySubRectSize.height - 1 - yInSubRect;
+
+ let src_y = yInSubRect + reifySrcOrigin.y;
+
+ // Test might generate flipped source based on srcPixels, e.g. Create ImageBitmap based on srcPixels but set orientation to 'flipY'
+ // Get candidate src pixel position in y by mirroring in source.
+ if (flipSrcBeforeCopy) src_y = reifySrcSize.height - src_y - 1;
+
+ const pixelPos =
+ src_y * reifySrcSize.width + (coords.x - reifyDstOrigin.x) + reifySrcOrigin.x;
+
+ const rgba = {
+ R: srcPixels[pixelPos * 4] / divide,
+ G: srcPixels[pixelPos * 4 + 1] / divide,
+ B: srcPixels[pixelPos * 4 + 2] / divide,
+ A: srcPixels[pixelPos * 4 + 3] / divide
+ };
+ applyConversion(rgba);
+ return rgba;
+ },
+ { clampToFormatRange: true }
+ );
+ }
+
+ doTestAndCheckResult(
+ imageCopyExternalImage,
+ dstTextureCopyView,
+ expTexelView,
+ copySize,
+ texelCompareOptions)
+ {
+ this.device.queue.copyExternalImageToTexture(
+ imageCopyExternalImage,
+ dstTextureCopyView,
+ copySize
+ );
+
+ this.expectTexelViewComparisonIsOkInTexture(
+ { texture: dstTextureCopyView.texture, origin: dstTextureCopyView.origin },
+ expTexelView,
+ copySize,
+ texelCompareOptions
+ );
+ this.trackForCleanup(dstTextureCopyView.texture);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/create_elements.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/create_elements.js
new file mode 100644
index 0000000000..722808c8b0
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/create_elements.js
@@ -0,0 +1,82 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { unreachable } from '../../common/util/util.js';
+// TESTING_TODO: This should expand to more canvas types (which will enhance a bunch of tests):
+// - canvas element not in dom
+// - canvas element in dom
+// - offscreen canvas from transferControlToOffscreen from canvas not in dom
+// - offscreen canvas from transferControlToOffscreen from canvas in dom
+// - offscreen canvas from new OffscreenCanvas
+export const kAllCanvasTypes = ['onscreen', 'offscreen'];
+
+
+
+
+
+
+
+/** Valid contextId for HTMLCanvasElement/OffscreenCanvas,
+ * spec: https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext
+ */
+export const kValidCanvasContextIds = [
+'2d',
+'bitmaprenderer',
+'webgl',
+'webgl2',
+'webgpu'];
+
+
+
+/** Create HTMLCanvas/OffscreenCanvas. */
+export function createCanvas(
+test,
+canvasType,
+width,
+height)
+{
+ if (canvasType === 'onscreen') {
+ if (typeof document !== 'undefined') {
+ return createOnscreenCanvas(test, width, height);
+ } else {
+ test.skip('Cannot create HTMLCanvasElement');
+ }
+ } else if (canvasType === 'offscreen') {
+ if (typeof OffscreenCanvas !== 'undefined') {
+ return createOffscreenCanvas(test, width, height);
+ } else {
+ test.skip('Cannot create an OffscreenCanvas');
+ }
+ } else {
+ unreachable();
+ }
+}
+
+/** Create HTMLCanvasElement. */
+export function createOnscreenCanvas(
+test,
+width,
+height)
+{
+ let canvas;
+ if (typeof document !== 'undefined') {
+ canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ } else {
+ test.skip('Cannot create HTMLCanvasElement');
+ }
+ return canvas;
+}
+
+/** Create OffscreenCanvas. */
+export function createOffscreenCanvas(
+test,
+width,
+height)
+{
+ if (typeof OffscreenCanvas === 'undefined') {
+ test.skip('OffscreenCanvas is not supported');
+ }
+
+ return new OffscreenCanvas(width, height);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/device_pool.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/device_pool.js
new file mode 100644
index 0000000000..feb62bc1be
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/device_pool.js
@@ -0,0 +1,414 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { SkipTestCase } from '../../common/framework/fixture.js';import { attemptGarbageCollection } from '../../common/util/collect_garbage.js';import { getGPU, getDefaultRequestAdapterOptions } from '../../common/util/navigator_gpu.js';
+import {
+ assert,
+ raceWithRejectOnTimeout,
+ assertReject,
+ unreachable } from
+'../../common/util/util.js';
+import { getDefaultLimits, kLimits } from '../capability_info.js';
+
+
+
+
+
+
+class TestFailedButDeviceReusable extends Error {}
+class FeaturesNotSupported extends Error {}
+export class TestOOMedShouldAttemptGC extends Error {}
+
+export class DevicePool {
+ holders = 'uninitialized';
+
+ /** Acquire a device from the pool and begin the error scopes. */
+ async acquire(
+ recorder,
+ descriptor)
+ {
+ let errorMessage = '';
+ if (this.holders === 'uninitialized') {
+ this.holders = new DescriptorToHolderMap();
+ try {
+ await this.holders.getOrCreate(recorder, undefined);
+ } catch (ex) {
+ this.holders = 'failed';
+ if (ex instanceof Error) {
+ errorMessage = ` with ${ex.name} "${ex.message}"`;
+ }
+ }
+ }
+
+ assert(
+ this.holders !== 'failed',
+ `WebGPU device failed to initialize${errorMessage}; not retrying`
+ );
+
+ const holder = await this.holders.getOrCreate(recorder, descriptor);
+
+ assert(holder.state === 'free', 'Device was in use on DevicePool.acquire');
+ holder.state = 'acquired';
+ holder.beginTestScope();
+ return holder;
+ }
+
+ /**
+ * End the error scopes and check for errors.
+ * Then, if the device seems reusable, release it back into the pool. Otherwise, drop it.
+ */
+ async release(holder) {
+ assert(this.holders instanceof DescriptorToHolderMap, 'DevicePool got into a bad state');
+ assert(holder instanceof DeviceHolder, 'DeviceProvider should always be a DeviceHolder');
+
+ assert(holder.state === 'acquired', 'trying to release a device while already released');
+ try {
+ await holder.endTestScope();
+
+ // (Hopefully if the device was lost, it has been reported by the time endErrorScopes()
+ // has finished (or timed out). If not, it could cause a finite number of extra test
+ // failures following this one (but should recover eventually).)
+ assert(
+ holder.lostInfo === undefined,
+ `Device was unexpectedly lost. Reason: ${holder.lostInfo?.reason}, Message: ${holder.lostInfo?.message}`
+ );
+ } catch (ex) {
+ // Any error that isn't explicitly TestFailedButDeviceReusable forces a new device to be
+ // created for the next test.
+ if (!(ex instanceof TestFailedButDeviceReusable)) {
+ this.holders.delete(holder);
+ if ('destroy' in holder.device) {
+ holder.device.destroy();
+ }
+
+ // Release the (hopefully only) ref to the GPUDevice.
+ holder.releaseGPUDevice();
+
+ // Try to clean up, in case there are stray GPU resources in need of collection.
+ if (ex instanceof TestOOMedShouldAttemptGC) {
+ await attemptGarbageCollection();
+ }
+ }
+ // In the try block, we may throw an error if the device is lost in order to force device
+ // reinitialization, however, if the device lost was expected we want to suppress the error
+ // The device lost is expected when `holder.expectedLostReason` is equal to
+ // `holder.lostInfo.reason`.
+ const expectedDeviceLost =
+ holder.expectedLostReason !== undefined &&
+ holder.lostInfo !== undefined &&
+ holder.expectedLostReason === holder.lostInfo.reason;
+ if (!expectedDeviceLost) {
+ throw ex;
+ }
+ } finally {
+ // Mark the holder as free so the device can be reused (if it's still in this.devices).
+ holder.state = 'free';
+ }
+ }
+}
+
+/**
+ * Map from GPUDeviceDescriptor to DeviceHolder.
+ */
+class DescriptorToHolderMap {
+ /** Map keys that are known to be unsupported and can be rejected quickly. */
+ unsupported = new Set();
+ holders = new Map();
+
+ /** Deletes an item from the map by DeviceHolder value. */
+ delete(holder) {
+ for (const [k, v] of this.holders) {
+ if (v === holder) {
+ this.holders.delete(k);
+ return;
+ }
+ }
+ unreachable("internal error: couldn't find DeviceHolder to delete");
+ }
+
+ /**
+ * Gets a DeviceHolder from the map if it exists; otherwise, calls create() to create one,
+ * inserts it, and returns it.
+ *
+ * If an `uncanonicalizedDescriptor` is provided, it is canonicalized and used as the map key.
+ * If one is not provided, the map key is `""` (empty string).
+ *
+ * Throws SkipTestCase if devices with this descriptor are unsupported.
+ */
+ async getOrCreate(
+ recorder,
+ uncanonicalizedDescriptor)
+ {
+ const [descriptor, key] = canonicalizeDescriptor(uncanonicalizedDescriptor);
+ // Quick-reject descriptors that are known to be unsupported already.
+ if (this.unsupported.has(key)) {
+ throw new SkipTestCase(
+ `GPUDeviceDescriptor previously failed: ${JSON.stringify(descriptor)}`
+ );
+ }
+
+ // Search for an existing device with the same descriptor.
+ {
+ const value = this.holders.get(key);
+ if (value) {
+ // Move it to the end of the Map (most-recently-used).
+ this.holders.delete(key);
+ this.holders.set(key, value);
+ return value;
+ }
+ }
+
+ // No existing item was found; add a new one.
+ let value;
+ try {
+ value = await DeviceHolder.create(recorder, descriptor);
+ } catch (ex) {
+ if (ex instanceof FeaturesNotSupported) {
+ this.unsupported.add(key);
+ throw new SkipTestCase(
+ `GPUDeviceDescriptor not supported: ${JSON.stringify(descriptor)}\n${ex?.message ?? ''}`
+ );
+ }
+
+ throw ex;
+ }
+ this.insertAndCleanUp(key, value);
+ return value;
+ }
+
+ /** Insert an entry, then remove the least-recently-used items if there are too many. */
+ insertAndCleanUp(key, value) {
+ this.holders.set(key, value);
+
+ const kMaxEntries = 5;
+ if (this.holders.size > kMaxEntries) {
+ // Delete the first (least recently used) item in the set.
+ for (const [key] of this.holders) {
+ this.holders.delete(key);
+ return;
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * Make a stringified map-key from a GPUDeviceDescriptor.
+ * Tries to make sure all defaults are resolved, first - but it's okay if some are missed
+ * (it just means some GPUDevice objects won't get deduplicated).
+ *
+ * This does **not** canonicalize `undefined` (the "default" descriptor) into a fully-qualified
+ * GPUDeviceDescriptor. This is just because `undefined` is a common case and we want to use it
+ * as a sanity check that WebGPU is working.
+ */
+function canonicalizeDescriptor(
+desc)
+{
+ if (desc === undefined) {
+ return [undefined, ''];
+ }
+
+ const featuresCanonicalized = desc.requiredFeatures ?
+ Array.from(new Set(desc.requiredFeatures)).sort() :
+ [];
+
+ /** Canonicalized version of the requested limits: in canonical order, with only values which are
+ * specified _and_ non-default. */
+ const limitsCanonicalized = {};
+ // MAINTENANCE_TODO: Remove cast when @webgpu/types includes compatibilityMode
+ const adapterOptions = getDefaultRequestAdapterOptions();
+
+
+ const featureLevel = adapterOptions?.compatibilityMode ? 'compatibility' : 'core';
+ const defaultLimits = getDefaultLimits(featureLevel);
+ if (desc.requiredLimits) {
+ for (const limit of kLimits) {
+ const requestedValue = desc.requiredLimits[limit];
+ const defaultValue = defaultLimits[limit].default;
+ // Skip adding a limit to limitsCanonicalized if it is the same as the default.
+ if (requestedValue !== undefined && requestedValue !== defaultValue) {
+ limitsCanonicalized[limit] = requestedValue;
+ }
+ }
+ }
+
+ // Type ensures every field is carried through.
+ const descriptorCanonicalized = {
+ requiredFeatures: featuresCanonicalized,
+ requiredLimits: limitsCanonicalized,
+ defaultQueue: {}
+ };
+ return [descriptorCanonicalized, JSON.stringify(descriptorCanonicalized)];
+}
+
+function supportsFeature(
+adapter,
+descriptor)
+{
+ if (descriptor === undefined) {
+ return true;
+ }
+
+ for (const feature of descriptor.requiredFeatures) {
+ if (!adapter.features.has(feature)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * DeviceHolder has three states:
+ * - 'free': Free to be used for a new test.
+ * - 'acquired': In use by a running test.
+ */
+
+
+/**
+ * Holds a GPUDevice and tracks its state (free/acquired) and handles device loss.
+ */
+class DeviceHolder {
+ /** The device. Will be cleared during cleanup if there were unexpected errors. */
+
+ /** Whether the device is in use by a test or not. */
+ state = 'free';
+ /** initially undefined; becomes set when the device is lost */
+
+ /** Set if the device is expected to be lost. */
+
+
+ // Gets a device and creates a DeviceHolder.
+ // If the device is lost, DeviceHolder.lost gets set.
+ static async create(
+ recorder,
+ descriptor)
+ {
+ const gpu = getGPU(recorder);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null, 'requestAdapter returned null');
+ if (!supportsFeature(adapter, descriptor)) {
+ throw new FeaturesNotSupported('One or more features are not supported');
+ }
+ const device = await adapter.requestDevice(descriptor);
+ assert(device !== null, 'requestDevice returned null');
+
+ return new DeviceHolder(device);
+ }
+
+ constructor(device) {
+ this._device = device;
+ void this._device.lost.then((ev) => {
+ this.lostInfo = ev;
+ });
+ }
+
+ get device() {
+ assert(this._device !== undefined);
+ return this._device;
+ }
+
+ /** Push error scopes that surround test execution. */
+ beginTestScope() {
+ assert(this.state === 'acquired');
+ this.device.pushErrorScope('validation');
+ this.device.pushErrorScope('internal');
+ this.device.pushErrorScope('out-of-memory');
+ }
+
+ /** Mark the DeviceHolder as expecting a device loss when the test scope ends. */
+ expectDeviceLost(reason) {
+ assert(this.state === 'acquired');
+ this.expectedLostReason = reason;
+ }
+
+ /**
+ * Attempt to end test scopes: Check that there are no extra error scopes, and that no
+ * otherwise-uncaptured errors occurred during the test. Time out if it takes too long.
+ */
+ endTestScope() {
+ assert(this.state === 'acquired');
+ const kTimeout = 5000;
+
+ // Time out if attemptEndTestScope (popErrorScope or onSubmittedWorkDone) never completes. If
+ // this rejects, the device won't be reused, so it's OK that popErrorScope calls may not have
+ // finished.
+ //
+ // This could happen due to a browser bug - e.g.,
+ // as of this writing, on Chrome GPU process crash, popErrorScope just hangs.
+ return raceWithRejectOnTimeout(this.attemptEndTestScope(), kTimeout, 'endTestScope timed out');
+ }
+
+ async attemptEndTestScope() {
+ let gpuValidationError;
+ let gpuInternalError;
+ let gpuOutOfMemoryError;
+
+ // Submit to the queue to attempt to force a GPU flush.
+ this.device.queue.submit([]);
+
+ try {
+ // May reject if the device was lost.
+ [gpuOutOfMemoryError, gpuInternalError, gpuValidationError] = await Promise.all([
+ this.device.popErrorScope(),
+ this.device.popErrorScope(),
+ this.device.popErrorScope()]
+ );
+ } catch (ex) {
+ assert(this.lostInfo !== undefined, 'popErrorScope failed; did beginTestScope get missed?');
+ throw ex;
+ }
+
+ // Attempt to wait for the queue to be idle.
+ if (this.device.queue.onSubmittedWorkDone) {
+ await this.device.queue.onSubmittedWorkDone();
+ }
+
+ await assertReject('OperationError', this.device.popErrorScope(), {
+ allowMissingStack: true,
+ message: 'There was an extra error scope on the stack after a test'
+ });
+
+ if (gpuOutOfMemoryError !== null) {
+ assert(gpuOutOfMemoryError instanceof GPUOutOfMemoryError);
+ // Don't allow the device to be reused; unexpected OOM could break the device.
+ throw new TestOOMedShouldAttemptGC('Unexpected out-of-memory error occurred');
+ }
+ if (gpuInternalError !== null) {
+ assert(gpuInternalError instanceof GPUInternalError);
+ // Allow the device to be reused.
+ throw new TestFailedButDeviceReusable(
+ `Unexpected internal error occurred: ${gpuInternalError.message}`
+ );
+ }
+ if (gpuValidationError !== null) {
+ assert(gpuValidationError instanceof GPUValidationError);
+ // Allow the device to be reused.
+ throw new TestFailedButDeviceReusable(
+ `Unexpected validation error occurred: ${gpuValidationError.message}`
+ );
+ }
+ }
+
+ /**
+ * Release the ref to the GPUDevice. This should be the only ref held by the DevicePool or
+ * GPUTest, so in theory it can get garbage collected.
+ */
+ releaseGPUDevice() {
+ this._device = undefined;
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/floating_point.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/floating_point.js
new file mode 100644
index 0000000000..aada64711c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/floating_point.js
@@ -0,0 +1,5441 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../common/util/util.js';import { Float16Array } from '../../external/petamoriken/float16/float16.js';
+
+
+
+import { anyOf } from './compare.js';
+import { kValue } from './constants.js';
+import {
+ abstractFloat,
+ f16,
+ f32,
+ isFloatType,
+
+
+ toMatrix,
+ toVector,
+ u32 } from
+'./conversion.js';
+import {
+ calculatePermutations,
+ cartesianProduct,
+ correctlyRoundedF16,
+ correctlyRoundedF32,
+ correctlyRoundedF64,
+ flatten2DArray,
+
+ flushSubnormalNumberF16,
+ flushSubnormalNumberF32,
+ flushSubnormalNumberF64,
+ isFiniteF16,
+ isFiniteF32,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ isSubnormalNumberF64,
+ map2DArray,
+ oneULPF16,
+ oneULPF32,
+ quantizeToF32,
+ quantizeToF16,
+ unflatten2DArray,
+ every2DArray } from
+'./math.js';
+
+/** Indicate the kind of WGSL floating point numbers being operated on */var
+
+
+SerializedFPIntervalKind = /*#__PURE__*/function (SerializedFPIntervalKind) {SerializedFPIntervalKind[SerializedFPIntervalKind["Abstract"] = 0] = "Abstract";SerializedFPIntervalKind[SerializedFPIntervalKind["F32"] = 1] = "F32";SerializedFPIntervalKind[SerializedFPIntervalKind["F16"] = 2] = "F16";return SerializedFPIntervalKind;}(SerializedFPIntervalKind || {});
+
+
+
+
+
+/** serializeFPKind() serializes a FPKind to a BinaryStream */
+export function serializeFPKind(s, value) {
+ switch (value) {
+ case 'abstract':
+ s.writeU8(SerializedFPIntervalKind.Abstract);
+ break;
+ case 'f16':
+ s.writeU8(SerializedFPIntervalKind.F16);
+ break;
+ case 'f32':
+ s.writeU8(SerializedFPIntervalKind.F32);
+ break;
+ }
+}
+
+/** deserializeFPKind() deserializes a FPKind from a BinaryStream */
+export function deserializeFPKind(s) {
+ const kind = s.readU8();
+ switch (kind) {
+ case SerializedFPIntervalKind.Abstract:
+ return 'abstract';
+ case SerializedFPIntervalKind.F16:
+ return 'f16';
+ case SerializedFPIntervalKind.F32:
+ return 'f32';
+ default:
+ unreachable(`invalid deserialized FPKind: ${kind}`);
+ }
+}
+// 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.
+ */
+
+
+/** Represents a closed interval of floating point numbers */
+export class FPInterval {
+
+
+
+
+ /**
+ * Constructor
+ *
+ * `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
+ */
+ constructor(kind, ...bounds) {
+ 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})`);
+
+ this.begin = begin;
+ this.end = end;
+ }
+
+ /** @returns the floating point traits for this interval */
+ traits() {
+ return FP[this.kind];
+ }
+
+ /** @returns begin and end if non-point interval, otherwise just begin */
+ bounds() {
+ return this.isPoint() ? [this.begin] : [this.begin, this.end];
+ }
+
+ /** @returns if a point or interval is completely contained by this interval */
+ contains(n) {
+ if (Number.isNaN(n)) {
+ // Being the 'any' interval indicates that accuracy is not defined for this
+ // test, so the test is just checking that this input doesn't cause the
+ // implementation to misbehave, so NaN is accepted.
+ return this.begin === Number.NEGATIVE_INFINITY && this.end === Number.POSITIVE_INFINITY;
+ }
+
+ if (n instanceof FPInterval) {
+ return this.begin <= n.begin && this.end >= n.end;
+ }
+ return this.begin <= n && this.end >= n;
+ }
+
+ /** @returns if any values in the interval may be flushed to zero, this
+ * includes any subnormals and zero itself.
+ */
+ containsZeroOrSubnormals() {
+ return !(
+ this.end < this.traits().constants().negative.subnormal.min ||
+ this.begin > this.traits().constants().positive.subnormal.max);
+
+ }
+
+ /** @returns if this interval contains a single point */
+ isPoint() {
+ return this.begin === this.end;
+ }
+
+ /** @returns if this interval only contains finite values */
+ isFinite() {
+ return this.traits().isFinite(this.begin) && this.traits().isFinite(this.end);
+ }
+
+ /** @returns a string representation for logging purposes */
+ toString() {
+ return `{ '${this.kind}', [${this.bounds().map(this.traits().scalarBuilder)}] }`;
+ }
+}
+
+/** serializeFPInterval() serializes a FPInterval to a BinaryStream */
+export function serializeFPInterval(s, i) {
+ serializeFPKind(s, i.kind);
+ const traits = FP[i.kind];
+ s.writeCond(i !== traits.constants().unboundedInterval, {
+ if_true: () => {
+ // Bounded
+ switch (i.kind) {
+ case 'abstract':
+ s.writeF64(i.begin);
+ s.writeF64(i.end);
+ break;
+ case 'f32':
+ s.writeF32(i.begin);
+ s.writeF32(i.end);
+ break;
+ case 'f16':
+ s.writeF16(i.begin);
+ s.writeF16(i.end);
+ break;
+ default:
+ unreachable(`Unable to serialize FPInterval ${i}`);
+ break;
+ }
+ },
+ if_false: () => {
+
+ // Unbounded
+ } });
+}
+
+/** deserializeFPInterval() deserializes a FPInterval from a BinaryStream */
+export function deserializeFPInterval(s) {
+ const kind = deserializeFPKind(s);
+ const traits = FP[kind];
+ return s.readCond({
+ if_true: () => {
+ // Bounded
+ switch (kind) {
+ case 'abstract':
+ return new FPInterval(traits.kind, s.readF64(), s.readF64());
+ case 'f32':
+ return new FPInterval(traits.kind, s.readF32(), s.readF32());
+ case 'f16':
+ return new FPInterval(traits.kind, s.readF16(), s.readF16());
+ }
+ unreachable(`Unable to deserialize FPInterval with kind ${kind}`);
+ },
+ if_false: () => {
+ // Unbounded
+ return traits.constants().unboundedInterval;
+ }
+ });
+}
+
+/**
+ * Representation of a vec2/3/4 of floating point intervals as an array of
+ * FPIntervals.
+ */
+
+
+
+
+
+/** Shorthand for an Array of Arrays that contains a column-major matrix */
+
+
+/**
+ * Representation of a matCxR of floating point intervals as an array of arrays
+ * of FPIntervals. This maps onto the WGSL concept of matrix. Internally
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Utilities
+
+/** @returns input with an appended 0, if inputs contains non-zero subnormals */
+// When f16 traits is defined, this can be replaced with something like
+// `FP.f16..addFlushIfNeeded`
+function addFlushedIfNeededF16(values) {
+ return values.some((v) => v !== 0 && isSubnormalNumberF16(v)) ? values.concat(0) : values;
+}
+
+// Operations
+
+/**
+ * A function that converts a point to an acceptance interval.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a ScalarToInterval */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * A function that converts a pair of points to an acceptance interval.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a ScalarPairToInterval */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** Domain for a ScalarPairToInterval implementation */
+
+
+
+
+
+
+/**
+ * A function that converts a triplet of points to an acceptance interval.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a ScalarTripleToInterval */
+
+
+
+
+
+
+// Currently ScalarToVector is not integrated with the rest of the floating point
+// framework, because the only builtins that use it are actually
+// u32 -> [f32, f32, f32, f32] functions, so the whole rounding and interval
+// process doesn't get applied to the inputs.
+// They do use the framework internally by invoking divisionInterval on segments
+// of the input.
+/**
+ * A function that converts a point to a vector of acceptance intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a vector to an acceptance interval.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a VectorToInterval */
+
+
+
+
+
+
+/**
+ * A function that converts a pair of vectors to an acceptance interval.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a VectorPairToInterval */
+
+
+
+
+
+
+/**
+ * A function that converts a vector to a vector of acceptance intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a VectorToVector */
+
+
+
+
+
+
+/**
+ * A function that converts a pair of vectors to a vector of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a VectorPairToVector */
+
+
+
+
+
+
+/**
+ * A function that converts a vector and a scalar to a vector of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a scalar and a vector to a vector of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a matrix to an acceptance interval.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/** Operation used to implement a MatrixToMatrix */
+
+
+
+
+
+
+/**
+ * A function that converts a matrix to a matrix of acceptance intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a pair of matrices to a matrix of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a matrix and a scalar to a matrix of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a scalar and a matrix to a matrix of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a matrix and a vector to a vector of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+/**
+ * A function that converts a vector and a matrix to a vector of acceptance
+ * intervals.
+ * This is the public facing API for builtin implementations that is called
+ * from tests.
+ */
+
+
+
+
+// Traits
+
+/**
+ * Typed structure containing all the limits/constants defined for each
+ * WGSL floating point kind
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** A representation of an FPInterval for a case param */
+
+
+
+
+
+/** Abstract base class for all floating-point traits */
+export class FPTraits {
+
+ constructor(k) {
+ this.kind = k;
+ }
+
+
+
+ // Utilities - Implemented
+
+ /** @returns an interval containing the point or the original interval */
+ toInterval(n) {
+ if (n instanceof FPInterval) {
+ if (n.kind === this.kind) {
+ return n;
+ }
+
+ // Preserve if the original interval was unbounded or bounded
+ if (!n.isFinite()) {
+ return this.constants().unboundedInterval;
+ }
+
+ return new FPInterval(this.kind, ...n.bounds());
+ }
+
+ if (n instanceof Array) {
+ return new FPInterval(this.kind, ...n);
+ }
+
+ return new FPInterval(this.kind, n, n);
+ }
+
+ /**
+ * Makes a param that can be turned into an interval
+ */
+ toParam(n) {
+ return {
+ kind: this.kind,
+ interval: n
+ };
+ }
+
+ /**
+ * Converts p into an FPInterval if it is an FPIntervalPAram
+ */
+ fromParam(
+ p)
+ {
+ const param = p;
+ if (param.interval && param.kind) {
+ assert(param.kind === this.kind);
+ return this.toInterval(param.interval);
+ }
+ return p;
+ }
+
+ /**
+ * @returns an interval with the tightest bounds that includes all provided
+ * intervals
+ */
+ spanIntervals(...intervals) {
+ assert(intervals.length > 0, `span of an empty list of FPIntervals is not allowed`);
+ assert(
+ intervals.every((i) => i.kind === this.kind),
+ `span is only defined for intervals with the same kind`
+ );
+ let begin = Number.POSITIVE_INFINITY;
+ let end = Number.NEGATIVE_INFINITY;
+ intervals.forEach((i) => {
+ begin = Math.min(i.begin, begin);
+ end = Math.max(i.end, end);
+ });
+ return this.toInterval([begin, end]);
+ }
+
+ /** Narrow an array of values to FPVector if possible */
+ isVector(v) {
+ if (v.every((e) => e instanceof FPInterval && e.kind === this.kind)) {
+ return v.length === 2 || v.length === 3 || v.length === 4;
+ }
+ return false;
+ }
+
+ /** @returns an FPVector representation of an array of values if possible */
+ toVector(v) {
+ if (this.isVector(v) && v.every((e) => e.kind === this.kind)) {
+ return v;
+ }
+
+ const f = v.map((e) => this.toInterval(e));
+ // The return of the map above is a readonly FPInterval[], which needs to be narrowed
+ // to FPVector, since FPVector is defined as fixed length tuples.
+ if (this.isVector(f)) {
+ return f;
+ }
+ unreachable(`Cannot convert [${v}] to FPVector`);
+ }
+
+ /**
+ * @returns a FPVector where each element is the span for corresponding
+ * elements at the same index in the input vectors
+ */
+ spanVectors(...vectors) {
+ assert(
+ vectors.every((e) => this.isVector(e)),
+ 'Vector span is not defined for vectors of differing floating point kinds'
+ );
+
+ const vector_length = vectors[0].length;
+ assert(
+ vectors.every((e) => e.length === vector_length),
+ `Vector span is not defined for vectors of differing lengths`
+ );
+
+ const result = new Array(vector_length);
+
+ for (let i = 0; i < vector_length; i++) {
+ result[i] = this.spanIntervals(...vectors.map((v) => v[i]));
+ }
+ return this.toVector(result);
+ }
+
+ /** Narrow an array of an array of values to FPMatrix if possible */
+ isMatrix(m) {
+ if (!m.every((c) => c.every((e) => e instanceof FPInterval && e.kind === this.kind))) {
+ return false;
+ }
+ // At this point m guaranteed to be a ROArrayArray<FPInterval>, but maybe typed as a
+ // FPVector[].
+ // Coercing the type since FPVector[] is functionally equivalent to
+ // ROArrayArray<FPInterval> for .length and .every, but they are type compatible,
+ // since tuples are not equivalent to arrays, so TS considers c in .every to
+ // be unresolvable below, even though our usage is safe.
+ m = m;
+
+ if (m.length > 4 || m.length < 2) {
+ return false;
+ }
+
+ const num_rows = m[0].length;
+ if (num_rows > 4 || num_rows < 2) {
+ return false;
+ }
+
+ return m.every((c) => c.length === num_rows);
+ }
+
+ /** @returns an FPMatrix representation of an array of an array of values if possible */
+ toMatrix(m) {
+ if (
+ this.isMatrix(m) &&
+ every2DArray(m, (e) => {
+ return e.kind === this.kind;
+ }))
+ {
+ return m;
+ }
+
+ const result = map2DArray(m, this.toInterval.bind(this));
+
+ // The return of the map above is a ROArrayArray<FPInterval>, which needs to be
+ // narrowed to FPMatrix, since FPMatrix is defined as fixed length tuples.
+ if (this.isMatrix(result)) {
+ return result;
+ }
+ unreachable(`Cannot convert ${m} to FPMatrix`);
+ }
+
+ /**
+ * @returns a FPMatrix where each element is the span for corresponding
+ * elements at the same index in the input matrices
+ */
+ spanMatrices(...matrices) {
+ // Coercing the type of matrices, since tuples are not generally compatible
+ // with Arrays, but they are functionally equivalent for the usages in this
+ // function.
+ const ms = matrices;
+ const num_cols = ms[0].length;
+ const num_rows = ms[0][0].length;
+ assert(
+ ms.every((m) => m.length === num_cols && m.every((r) => r.length === num_rows)),
+ `Matrix span is not defined for Matrices of differing dimensions`
+ );
+
+ const result = [...Array(num_cols)].map((_) => [...Array(num_rows)]);
+ for (let i = 0; i < num_cols; i++) {
+ for (let j = 0; j < num_rows; j++) {
+ result[i][j] = this.spanIntervals(...ms.map((m) => m[i][j]));
+ }
+ }
+
+ return this.toMatrix(result);
+ }
+
+ /** @returns input with an appended 0, if inputs contains non-zero subnormals */
+ addFlushedIfNeeded(values) {
+ const subnormals = values.filter(this.isSubnormal);
+ const needs_zero = subnormals.length > 0 && subnormals.every((s) => s !== 0);
+ 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 */
+ limitScalarToIntervalDomain(
+ domain,
+ impl)
+ {
+ return (n) => {
+ 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 */
+ limitScalarPairToIntervalDomain(
+ domain,
+ impl)
+ {
+ return (x, y) => {
+ 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 */
+ unimplementedScalarToInterval(name, _x) {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for scalar pair to interval generator */
+ unimplementedScalarPairToInterval(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for scalar triple to interval generator */
+ unimplementedScalarTripleToInterval(
+ name,
+ _x,
+ _y,
+ _z)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for scalar to vector generator */
+ unimplementedScalarToVector(name, _x) {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for vector to interval generator */
+ unimplementedVectorToInterval(name, _x) {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for vector pair to interval generator */
+ unimplementedVectorPairToInterval(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for vector to vector generator */
+ unimplementedVectorToVector(
+ name,
+ _x)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for vector pair to vector generator */
+ unimplementedVectorPairToVector(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for vector-scalar to vector generator */
+ unimplementedVectorScalarToVector(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for scalar-vector to vector generator */
+ unimplementedScalarVectorToVector(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for matrix to interval generator */
+ unimplementedMatrixToInterval(name, _x) {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for matrix to matirx generator */
+ unimplementedMatrixToMatrix(name, _x) {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for matrix pair to matrix generator */
+ unimplementedMatrixPairToMatrix(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for matrix-scalar to matrix generator */
+ unimplementedMatrixScalarToMatrix(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for scalar-matrix to matrix generator */
+ unimplementedScalarMatrixToMatrix(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for matrix-vector to vector generator */
+ unimplementedMatrixVectorToVector(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for vector-matrix to vector generator */
+ unimplementedVectorMatrixToVector(
+ name,
+ _x,
+ _y)
+ {
+ unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for distance generator */
+ unimplementedDistance(
+ _x,
+ _y)
+ {
+ unreachable(`'distance' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for faceForward */
+ unimplementedFaceForward(
+ _x,
+ _y,
+ _z)
+ {
+ unreachable(`'faceForward' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for length generator */
+ unimplementedLength(
+ _x)
+ {
+ unreachable(`'length' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for modf generator */
+ unimplementedModf(_x) {
+ unreachable(`'modf' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Stub for refract generator */
+ unimplementedRefract(
+ _i,
+ _s,
+ _r)
+ {
+ unreachable(`'refract' is not yet implemented for '${this.kind}'`);
+ }
+
+ /** Version of absoluteErrorInterval that always returns the unboundedInterval */
+ unboundedAbsoluteErrorInterval(_n, _error_range) {
+ return this.constants().unboundedInterval;
+ }
+
+ /** Version of ulpInterval that always returns the unboundedInterval */
+ unboundedUlpInterval(_n, _numULP) {
+ return this.constants().unboundedInterval;
+ }
+
+ // Utilities - Defined by subclass
+ /**
+ * @returns the nearest precise value to the input. Rounding should be IEEE
+ * 'roundTiesToEven'.
+ */
+
+ /** @returns all valid roundings of input */
+
+ /** @returns true if input is considered finite, otherwise false */
+
+ /** @returns true if input is considered subnormal, otherwise false */
+
+ /** @returns 0 if the provided number is subnormal, otherwise returns the proved number */
+
+ /** @returns 1 * ULP: (number) */
+
+ /** @returns a builder for converting numbers to Scalars */
+
+
+ // Framework - Cases
+
+ /**
+ * @returns a Case for the param and the interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param the param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeScalarToIntervalCase(
+ param,
+ filter,
+ ...ops)
+ {
+ param = this.quantize(param);
+
+ const intervals = ops.map((o) => o(param));
+ if (filter === 'finite' && intervals.some((i) => !i.isFinite())) {
+ return undefined;
+ }
+ return { input: [this.scalarBuilder(param)], expected: anyOf(...intervals) };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param params array of inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateScalarToIntervalCases(
+ params,
+ filter,
+ ...ops)
+ {
+ return params.reduce((cases, e) => {
+ const c = this.makeScalarToIntervalCase(e, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and the interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param0 the first param to pass in
+ * @param param1 the second param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeScalarPairToIntervalCase(
+ param0,
+ param1,
+ filter,
+ ...ops)
+ {
+ param0 = this.quantize(param0);
+ param1 = this.quantize(param1);
+
+ const intervals = ops.map((o) => o(param0, param1));
+ if (filter === 'finite' && intervals.some((i) => !i.isFinite())) {
+ return undefined;
+ }
+ return {
+ input: [this.scalarBuilder(param0), this.scalarBuilder(param1)],
+ expected: anyOf(...intervals)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first input
+ * @param param1s array of inputs to try for the second input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateScalarPairToIntervalCases(
+ param0s,
+ param1s,
+ filter,
+ ...ops)
+ {
+ return cartesianProduct(param0s, param1s).reduce((cases, e) => {
+ const c = this.makeScalarPairToIntervalCase(e[0], e[1], filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and the interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param0 the first param to pass in
+ * @param param1 the second param to pass in
+ * @param param2 the third param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeScalarTripleToIntervalCase(
+ param0,
+ param1,
+ param2,
+ filter,
+ ...ops)
+ {
+ param0 = this.quantize(param0);
+ param1 = this.quantize(param1);
+ param2 = this.quantize(param2);
+
+ const intervals = ops.map((o) => o(param0, param1, param2));
+ if (filter === 'finite' && intervals.some((i) => !i.isFinite())) {
+ return undefined;
+ }
+ return {
+ input: [this.scalarBuilder(param0), this.scalarBuilder(param1), this.scalarBuilder(param2)],
+ expected: anyOf(...intervals)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first input
+ * @param param1s array of inputs to try for the second input
+ * @param param2s array of inputs to try for the third input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateScalarTripleToIntervalCases(
+ param0s,
+ param1s,
+ param2s,
+ filter,
+ ...ops)
+ {
+ return cartesianProduct(param0s, param1s, param2s).reduce((cases, e) => {
+ const c = this.makeScalarTripleToIntervalCase(e[0], e[1], e[2], filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and the interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param the param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeVectorToIntervalCase(
+ param,
+ filter,
+ ...ops)
+ {
+ param = param.map(this.quantize);
+
+ const intervals = ops.map((o) => o(param));
+ if (filter === 'finite' && intervals.some((i) => !i.isFinite())) {
+ return undefined;
+ }
+ return {
+ input: [toVector(param, this.scalarBuilder)],
+ expected: anyOf(...intervals)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param params array of inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateVectorToIntervalCases(
+ params,
+ filter,
+ ...ops)
+ {
+ return params.reduce((cases, e) => {
+ const c = this.makeVectorToIntervalCase(e, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and the interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param0 the first param to pass in
+ * @param param1 the second param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeVectorPairToIntervalCase(
+ param0,
+ param1,
+ filter,
+ ...ops)
+ {
+ param0 = param0.map(this.quantize);
+ param1 = param1.map(this.quantize);
+
+ const intervals = ops.map((o) => o(param0, param1));
+ if (filter === 'finite' && intervals.some((i) => !i.isFinite())) {
+ return undefined;
+ }
+ return {
+ input: [toVector(param0, this.scalarBuilder), toVector(param1, this.scalarBuilder)],
+ expected: anyOf(...intervals)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first input
+ * @param param1s array of inputs to try for the second input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateVectorPairToIntervalCases(
+ param0s,
+ param1s,
+ filter,
+ ...ops)
+ {
+ return cartesianProduct(param0s, param1s).reduce((cases, e) => {
+ const c = this.makeVectorPairToIntervalCase(e[0], e[1], filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the param and vector of intervals generator provided
+ * @param param the param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals.
+ */
+ makeVectorToVectorCase(
+ param,
+ filter,
+ ...ops)
+ {
+ param = param.map(this.quantize);
+
+ const vectors = ops.map((o) => o(param));
+ if (filter === 'finite' && vectors.some((v) => v.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [toVector(param, this.scalarBuilder)],
+ expected: anyOf(...vectors)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param params array of inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals.
+ */
+ generateVectorToVectorCases(
+ params,
+ filter,
+ ...ops)
+ {
+ return params.reduce((cases, e) => {
+ const c = this.makeVectorToVectorCase(e, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and the interval vector generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param scalar the scalar param to pass in
+ * @param vector the vector param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance intervals
+ */
+ makeScalarVectorToVectorCase(
+ scalar,
+ vector,
+ filter,
+ ...ops)
+ {
+ scalar = this.quantize(scalar);
+ vector = vector.map(this.quantize);
+
+ const results = ops.map((o) => o(scalar, vector));
+ if (filter === 'finite' && results.some((r) => r.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [this.scalarBuilder(scalar), toVector(vector, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param scalars array of scalar inputs to try
+ * @param vectors array of vector inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance intervals
+ */
+ generateScalarVectorToVectorCases(
+ scalars,
+ vectors,
+ filter,
+ ...ops)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ scalars.forEach((scalar) => {
+ vectors.forEach((vector) => {
+ const c = this.makeScalarVectorToVectorCase(scalar, vector, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ return cases;
+ }
+
+ /**
+ * @returns a Case for the params and the interval vector generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param vector the vector param to pass in
+ * @param scalar the scalar param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance intervals
+ */
+ makeVectorScalarToVectorCase(
+ vector,
+ scalar,
+ filter,
+ ...ops)
+ {
+ vector = vector.map(this.quantize);
+ scalar = this.quantize(scalar);
+
+ const results = ops.map((o) => o(vector, scalar));
+ if (filter === 'finite' && results.some((r) => r.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [toVector(vector, this.scalarBuilder), this.scalarBuilder(scalar)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param vectors array of vector inputs to try
+ * @param scalars array of scalar inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance intervals
+ */
+ generateVectorScalarToVectorCases(
+ vectors,
+ scalars,
+ filter,
+ ...ops)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ vectors.forEach((vector) => {
+ scalars.forEach((scalar) => {
+ const c = this.makeVectorScalarToVectorCase(vector, scalar, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ return cases;
+ }
+
+ /**
+ * @returns a Case for the param and vector of intervals generator provided
+ * @param param0 the first param to pass in
+ * @param param1 the second param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals.
+ */
+ makeVectorPairToVectorCase(
+ param0,
+ param1,
+ filter,
+ ...ops)
+ {
+ param0 = param0.map(this.quantize);
+ param1 = param1.map(this.quantize);
+ const vectors = ops.map((o) => o(param0, param1));
+ if (filter === 'finite' && vectors.some((v) => v.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [toVector(param0, this.scalarBuilder), toVector(param1, this.scalarBuilder)],
+ expected: anyOf(...vectors)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first input
+ * @param param1s array of inputs to try for the second input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals.
+ */
+ generateVectorPairToVectorCases(
+ param0s,
+ param1s,
+ filter,
+ ...ops)
+ {
+ return cartesianProduct(param0s, param1s).reduce((cases, e) => {
+ const c = this.makeVectorPairToVectorCase(e[0], e[1], filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and the component-wise interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param0 the first vector param to pass in
+ * @param param1 the second vector param to pass in
+ * @param param2 the scalar param to pass in
+ * @param filter what interval filtering to apply
+ * @param componentWiseOps callbacks that implement generating a component-wise acceptance interval,
+ * one component result at a time.
+ */
+ makeVectorPairScalarToVectorComponentWiseCase(
+ param0,
+ param1,
+ param2,
+ filter,
+ ...componentWiseOps)
+ {
+ // Width of input vector
+ const width = param0.length;
+ assert(2 <= width && width <= 4, 'input vector width must between 2 and 4');
+ assert(param1.length === width, 'two input vectors must have the same width');
+ param0 = param0.map(this.quantize);
+ param1 = param1.map(this.quantize);
+ param2 = this.quantize(param2);
+
+ // Call the component-wise interval generator and build the expectation FPVector
+ const results = componentWiseOps.map((o) => {
+ return param0.map((el0, index) => o(el0, param1[index], param2));
+ });
+ if (filter === 'finite' && results.some((r) => r.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [
+ toVector(param0, this.scalarBuilder),
+ toVector(param1, this.scalarBuilder),
+ this.scalarBuilder(param2)],
+
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of first vector inputs to try
+ * @param param1s array of second vector inputs to try
+ * @param param2s array of scalar inputs to try
+ * @param filter what interval filtering to apply
+ * @param componentWiseOpscallbacks that implement generating a component-wise acceptance interval
+ */
+ generateVectorPairScalarToVectorComponentWiseCase(
+ param0s,
+ param1s,
+ param2s,
+ filter,
+ ...componentWiseOps)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ param0s.forEach((param0) => {
+ param1s.forEach((param1) => {
+ param2s.forEach((param2) => {
+ const c = this.makeVectorPairScalarToVectorComponentWiseCase(
+ param0,
+ param1,
+ param2,
+ filter,
+ ...componentWiseOps
+ );
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ });
+ return cases;
+ }
+
+ /**
+ * @returns a Case for the param and an array of interval generators provided
+ * @param param the param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeMatrixToScalarCase(
+ param,
+ filter,
+ ...ops)
+ {
+ param = map2DArray(param, this.quantize);
+
+ const results = ops.map((o) => o(param));
+ if (filter === 'finite' && results.some((e) => !e.isFinite())) {
+ return undefined;
+ }
+
+ return {
+ input: [toMatrix(param, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param params array of inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateMatrixToScalarCases(
+ params,
+ filter,
+ ...ops)
+ {
+ return params.reduce((cases, e) => {
+ const c = this.makeMatrixToScalarCase(e, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the param and an array of interval generators provided
+ * @param param the param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ makeMatrixToMatrixCase(
+ param,
+ filter,
+ ...ops)
+ {
+ param = map2DArray(param, this.quantize);
+
+ const results = ops.map((o) => o(param));
+ if (filter === 'finite' && results.some((m) => m.some((c) => c.some((r) => !r.isFinite())))) {
+ return undefined;
+ }
+
+ return {
+ input: [toMatrix(param, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param params array of inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ generateMatrixToMatrixCases(
+ params,
+ filter,
+ ...ops)
+ {
+ return params.reduce((cases, e) => {
+ const c = this.makeMatrixToMatrixCase(e, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and matrix of intervals generator provided
+ * @param param0 the first param to pass in
+ * @param param1 the second param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ makeMatrixPairToMatrixCase(
+ param0,
+ param1,
+ filter,
+ ...ops)
+ {
+ 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;
+ }
+ return {
+ input: [toMatrix(param0, this.scalarBuilder), toMatrix(param1, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first input
+ * @param param1s array of inputs to try for the second input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ generateMatrixPairToMatrixCases(
+ param0s,
+ param1s,
+ filter,
+ ...ops)
+ {
+ return cartesianProduct(param0s, param1s).reduce((cases, e) => {
+ const c = this.makeMatrixPairToMatrixCase(e[0], e[1], filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ /**
+ * @returns a Case for the params and matrix of intervals generator provided
+ * @param mat the matrix param to pass in
+ * @param scalar the scalar to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ makeMatrixScalarToMatrixCase(
+ mat,
+ scalar,
+ filter,
+ ...ops)
+ {
+ mat = map2DArray(mat, this.quantize);
+ scalar = this.quantize(scalar);
+
+ const results = ops.map((o) => o(mat, scalar));
+ if (filter === 'finite' && results.some((m) => m.some((c) => c.some((r) => !r.isFinite())))) {
+ return undefined;
+ }
+ return {
+ input: [toMatrix(mat, this.scalarBuilder), this.scalarBuilder(scalar)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param mats array of inputs to try for the matrix input
+ * @param scalars array of inputs to try for the scalar input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ generateMatrixScalarToMatrixCases(
+ mats,
+ scalars,
+ filter,
+ ...ops)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ mats.forEach((mat) => {
+ scalars.forEach((scalar) => {
+ const c = this.makeMatrixScalarToMatrixCase(mat, scalar, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ return cases;
+ }
+
+ /**
+ * @returns a Case for the params and matrix of intervals generator provided
+ * @param scalar the scalar to pass in
+ * @param mat the matrix param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ makeScalarMatrixToMatrixCase(
+ scalar,
+ mat,
+ filter,
+ ...ops)
+ {
+ scalar = this.quantize(scalar);
+ mat = map2DArray(mat, this.quantize);
+
+ const results = ops.map((o) => o(scalar, mat));
+ if (filter === 'finite' && results.some((m) => m.some((c) => c.some((r) => !r.isFinite())))) {
+ return undefined;
+ }
+ return {
+ input: [this.scalarBuilder(scalar), toMatrix(mat, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param scalars array of inputs to try for the scalar input
+ * @param mats array of inputs to try for the matrix input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a matrix of acceptance
+ * intervals
+ */
+ generateScalarMatrixToMatrixCases(
+ scalars,
+ mats,
+ filter,
+ ...ops)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ mats.forEach((mat) => {
+ scalars.forEach((scalar) => {
+ const c = this.makeScalarMatrixToMatrixCase(scalar, mat, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ return cases;
+ }
+
+ /**
+ * @returns a Case for the params and the vector of intervals generator provided
+ * @param mat the matrix param to pass in
+ * @param vec the vector to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals
+ */
+ makeMatrixVectorToVectorCase(
+ mat,
+ vec,
+ filter,
+ ...ops)
+ {
+ mat = map2DArray(mat, this.quantize);
+ vec = vec.map(this.quantize);
+
+ const results = ops.map((o) => o(mat, vec));
+ if (filter === 'finite' && results.some((v) => v.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [toMatrix(mat, this.scalarBuilder), toVector(vec, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param mats array of inputs to try for the matrix input
+ * @param vecs array of inputs to try for the vector input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals
+ */
+ generateMatrixVectorToVectorCases(
+ mats,
+ vecs,
+ filter,
+ ...ops)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ mats.forEach((mat) => {
+ vecs.forEach((vec) => {
+ const c = this.makeMatrixVectorToVectorCase(mat, vec, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ return cases;
+ }
+
+ /**
+ * @returns a Case for the params and the vector of intervals generator provided
+ * @param vec the vector to pass in
+ * @param mat the matrix param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals
+ */
+ makeVectorMatrixToVectorCase(
+ vec,
+ mat,
+ filter,
+ ...ops)
+ {
+ vec = vec.map(this.quantize);
+ mat = map2DArray(mat, this.quantize);
+
+ const results = ops.map((o) => o(vec, mat));
+ if (filter === 'finite' && results.some((v) => v.some((e) => !e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: [toVector(vec, this.scalarBuilder), toMatrix(mat, this.scalarBuilder)],
+ expected: anyOf(...results)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param vecs array of inputs to try for the vector input
+ * @param mats array of inputs to try for the matrix input
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating a vector of acceptance
+ * intervals
+ */
+ generateVectorMatrixToVectorCases(
+ vecs,
+ mats,
+ filter,
+ ...ops)
+ {
+ // Cannot use cartesianProduct here, due to heterogeneous types
+ const cases = [];
+ vecs.forEach((vec) => {
+ mats.forEach((mat) => {
+ const c = this.makeVectorMatrixToVectorCase(vec, mat, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ });
+ });
+ return cases;
+ }
+
+ // Framework - Intervals
+
+ /**
+ * Converts a point to an acceptance interval, using a specific function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ * op.extrema is invoked before this point in the call stack.
+ * op.domain is tested before this point in the call stack.
+ *
+ * @param n value to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ roundAndFlushScalarToInterval(n, op) {
+ assert(!Number.isNaN(n), `flush not defined for NaN`);
+ const values = this.correctlyRounded(n);
+ const inputs = this.addFlushedIfNeeded(values);
+ const results = new Set(inputs.map(op.impl));
+ return this.spanIntervals(...results);
+ }
+
+ /**
+ * Converts a pair to an acceptance interval, using a specific function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ * All unique combinations of x & y are run.
+ * op.extrema is invoked before this point in the call stack.
+ * op.domain is tested before this point in the call stack.
+ *
+ * @param x first param to flush & round then invoke op.impl on
+ * @param y second param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ roundAndFlushScalarPairToInterval(
+ x,
+ y,
+ op)
+ {
+ 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);
+ const intervals = new Set();
+ x_inputs.forEach((inner_x) => {
+ y_inputs.forEach((inner_y) => {
+ intervals.add(op.impl(inner_x, inner_y));
+ });
+ });
+ return this.spanIntervals(...intervals);
+ }
+
+ /**
+ * Converts a triplet to an acceptance interval, using a specific function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ * All unique combinations of x, y & z are run.
+ *
+ * @param x first param to flush & round then invoke op.impl on
+ * @param y second param to flush & round then invoke op.impl on
+ * @param z third param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ roundAndFlushScalarTripleToInterval(
+ x,
+ y,
+ z,
+ op)
+ {
+ assert(!Number.isNaN(x), `flush not defined for NaN`);
+ assert(!Number.isNaN(y), `flush not defined for NaN`);
+ assert(!Number.isNaN(z), `flush not defined for NaN`);
+ const x_values = this.correctlyRounded(x);
+ const y_values = this.correctlyRounded(y);
+ const z_values = this.correctlyRounded(z);
+ const x_inputs = this.addFlushedIfNeeded(x_values);
+ const y_inputs = this.addFlushedIfNeeded(y_values);
+ const z_inputs = this.addFlushedIfNeeded(z_values);
+ const intervals = new Set();
+
+ x_inputs.forEach((inner_x) => {
+ y_inputs.forEach((inner_y) => {
+ z_inputs.forEach((inner_z) => {
+ intervals.add(op.impl(inner_x, inner_y, inner_z));
+ });
+ });
+ });
+
+ return this.spanIntervals(...intervals);
+ }
+
+ /**
+ * Converts a vector to an acceptance interval using a specific function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ *
+ * @param x param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ roundAndFlushVectorToInterval(x, op) {
+ assert(
+ x.every((e) => !Number.isNaN(e)),
+ `flush not defined for NaN`
+ );
+
+ const x_rounded = x.map(this.correctlyRounded);
+ const x_flushed = x_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const x_inputs = cartesianProduct(...x_flushed);
+
+ const intervals = new Set();
+ x_inputs.forEach((inner_x) => {
+ intervals.add(op.impl(inner_x));
+ });
+ return this.spanIntervals(...intervals);
+ }
+
+ /**
+ * Converts a pair of vectors to an acceptance interval using a specific
+ * function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ * All unique combinations of x & y are run.
+ *
+ * @param x first param to flush & round then invoke op.impl on
+ * @param y second param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ roundAndFlushVectorPairToInterval(
+ x,
+ y,
+ op)
+ {
+ assert(
+ x.every((e) => !Number.isNaN(e)),
+ `flush not defined for NaN`
+ );
+ assert(
+ y.every((e) => !Number.isNaN(e)),
+ `flush not defined for NaN`
+ );
+
+ const x_rounded = x.map(this.correctlyRounded);
+ const y_rounded = y.map(this.correctlyRounded);
+ const x_flushed = x_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const y_flushed = y_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const x_inputs = cartesianProduct(...x_flushed);
+ const y_inputs = cartesianProduct(...y_flushed);
+
+ const intervals = new Set();
+ x_inputs.forEach((inner_x) => {
+ y_inputs.forEach((inner_y) => {
+ intervals.add(op.impl(inner_x, inner_y));
+ });
+ });
+ return this.spanIntervals(...intervals);
+ }
+
+ /**
+ * Converts a vector to a vector of acceptance intervals using a specific
+ * function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ *
+ * @param x param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a vector of spans for each outputs of op.impl
+ */
+ roundAndFlushVectorToVector(x, op) {
+ assert(
+ x.every((e) => !Number.isNaN(e)),
+ `flush not defined for NaN`
+ );
+
+ const x_rounded = x.map(this.correctlyRounded);
+ const x_flushed = x_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const x_inputs = cartesianProduct(...x_flushed);
+
+ const interval_vectors = new Set();
+ x_inputs.forEach((inner_x) => {
+ interval_vectors.add(op.impl(inner_x));
+ });
+
+ return this.spanVectors(...interval_vectors);
+ }
+
+ /**
+ * Converts a pair of vectors to a vector of acceptance intervals using a
+ * specific function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ *
+ * @param x first param to flush & round then invoke op.impl on
+ * @param y second param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a vector of spans for each output of op.impl
+ */
+ roundAndFlushVectorPairToVector(
+ x,
+ y,
+ op)
+ {
+ assert(
+ x.every((e) => !Number.isNaN(e)),
+ `flush not defined for NaN`
+ );
+ assert(
+ y.every((e) => !Number.isNaN(e)),
+ `flush not defined for NaN`
+ );
+
+ const x_rounded = x.map(this.correctlyRounded);
+ const y_rounded = y.map(this.correctlyRounded);
+ const x_flushed = x_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const y_flushed = y_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const x_inputs = cartesianProduct(...x_flushed);
+ const y_inputs = cartesianProduct(...y_flushed);
+
+ const interval_vectors = new Set();
+ x_inputs.forEach((inner_x) => {
+ y_inputs.forEach((inner_y) => {
+ interval_vectors.add(op.impl(inner_x, inner_y));
+ });
+ });
+
+ return this.spanVectors(...interval_vectors);
+ }
+
+ /**
+ * Converts a matrix to a matrix of acceptance intervals using a specific
+ * function
+ *
+ * This handles correctly rounding and flushing inputs as needed.
+ * Duplicate inputs are pruned before invoking op.impl.
+ *
+ * @param m param to flush & round then invoke op.impl on
+ * @param op operation defining the function being run
+ * @returns a matrix of spans for each outputs of op.impl
+ */
+ roundAndFlushMatrixToMatrix(m, op) {
+ const num_cols = m.length;
+ const num_rows = m[0].length;
+ assert(
+ m.every((c) => c.every((r) => !Number.isNaN(r))),
+ `flush not defined for NaN`
+ );
+
+ const m_flat = flatten2DArray(m);
+ const m_rounded = m_flat.map(this.correctlyRounded);
+ const m_flushed = m_rounded.map(this.addFlushedIfNeeded.bind(this));
+ const m_options = cartesianProduct(...m_flushed);
+ const m_inputs = m_options.map((e) =>
+ unflatten2DArray(e, num_cols, num_rows)
+ );
+
+ const interval_matrices = new Set();
+ m_inputs.forEach((inner_m) => {
+ interval_matrices.add(op.impl(inner_m));
+ });
+
+ return this.spanMatrices(...interval_matrices);
+ }
+
+ /**
+ * Calculate the acceptance interval for a unary function over an interval
+ *
+ * If the interval is actually a point, this just decays to
+ * roundAndFlushScalarToInterval.
+ *
+ * The provided domain interval may be adjusted if the operation defines an
+ * extrema function.
+ *
+ * @param x input domain interval
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ runScalarToIntervalOp(x, op) {
+ if (!x.isFinite()) {
+ return this.constants().unboundedInterval;
+ }
+
+ if (op.extrema !== undefined) {
+ x = op.extrema(x);
+ }
+
+ const result = this.spanIntervals(
+ ...x.bounds().map((b) => this.roundAndFlushScalarToInterval(b, op))
+ );
+ return result.isFinite() ? result : this.constants().unboundedInterval;
+ }
+
+ /**
+ * Calculate the acceptance interval for a binary function over an interval
+ *
+ * The provided domain intervals may be adjusted if the operation defines an
+ * extrema function.
+ *
+ * @param x first input domain interval
+ * @param y second input domain interval
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ runScalarPairToIntervalOp(
+ x,
+ y,
+ op)
+ {
+ if (!x.isFinite() || !y.isFinite()) {
+ return this.constants().unboundedInterval;
+ }
+
+ if (op.extrema !== undefined) {
+ [x, y] = op.extrema(x, y);
+ }
+
+ const outputs = new Set();
+ x.bounds().forEach((inner_x) => {
+ y.bounds().forEach((inner_y) => {
+ outputs.add(this.roundAndFlushScalarPairToInterval(inner_x, inner_y, op));
+ });
+ });
+
+ const result = this.spanIntervals(...outputs);
+ return result.isFinite() ? result : this.constants().unboundedInterval;
+ }
+
+ /**
+ * Calculate the acceptance interval for a ternary function over an interval
+ *
+ * @param x first input domain interval
+ * @param y second input domain interval
+ * @param z third input domain interval
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ runScalarTripleToIntervalOp(
+ x,
+ y,
+ z,
+ op)
+ {
+ if (!x.isFinite() || !y.isFinite() || !z.isFinite()) {
+ return this.constants().unboundedInterval;
+ }
+
+ const outputs = new Set();
+ x.bounds().forEach((inner_x) => {
+ y.bounds().forEach((inner_y) => {
+ z.bounds().forEach((inner_z) => {
+ outputs.add(this.roundAndFlushScalarTripleToInterval(inner_x, inner_y, inner_z, op));
+ });
+ });
+ });
+
+ const result = this.spanIntervals(...outputs);
+ return result.isFinite() ? result : this.constants().unboundedInterval;
+ }
+
+ /**
+ * Calculate the acceptance interval for a vector function over given
+ * intervals
+ *
+ * @param x input domain intervals vector
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ runVectorToIntervalOp(x, op) {
+ if (x.some((e) => !e.isFinite())) {
+ return this.constants().unboundedInterval;
+ }
+
+ const x_values = cartesianProduct(...x.map((e) => e.bounds()));
+
+ const outputs = new Set();
+ x_values.forEach((inner_x) => {
+ outputs.add(this.roundAndFlushVectorToInterval(inner_x, op));
+ });
+
+ const result = this.spanIntervals(...outputs);
+ return result.isFinite() ? result : this.constants().unboundedInterval;
+ }
+
+ /**
+ * Calculate the acceptance interval for a vector pair function over given
+ * intervals
+ *
+ * @param x first input domain intervals vector
+ * @param y second input domain intervals vector
+ * @param op operation defining the function being run
+ * @returns a span over all the outputs of op.impl
+ */
+ runVectorPairToIntervalOp(
+ x,
+ y,
+ op)
+ {
+ if (x.some((e) => !e.isFinite()) || y.some((e) => !e.isFinite())) {
+ return this.constants().unboundedInterval;
+ }
+
+ const x_values = cartesianProduct(...x.map((e) => e.bounds()));
+ const y_values = cartesianProduct(...y.map((e) => e.bounds()));
+
+ const outputs = new Set();
+ x_values.forEach((inner_x) => {
+ y_values.forEach((inner_y) => {
+ outputs.add(this.roundAndFlushVectorPairToInterval(inner_x, inner_y, op));
+ });
+ });
+
+ const result = this.spanIntervals(...outputs);
+ return result.isFinite() ? result : this.constants().unboundedInterval;
+ }
+
+ /**
+ * Calculate the vector of acceptance intervals for a pair of vector function
+ * over given intervals
+ *
+ * @param x input domain intervals vector
+ * @param op operation defining the function being run
+ * @returns a vector of spans over all the outputs of op.impl
+ */
+ runVectorToVectorOp(x, op) {
+ if (x.some((e) => !e.isFinite())) {
+ return this.constants().unboundedVector[x.length];
+ }
+
+ const x_values = cartesianProduct(...x.map((e) => e.bounds()));
+
+ const outputs = new Set();
+ x_values.forEach((inner_x) => {
+ outputs.add(this.roundAndFlushVectorToVector(inner_x, op));
+ });
+
+ const result = this.spanVectors(...outputs);
+ return result.every((e) => e.isFinite()) ?
+ result :
+ this.constants().unboundedVector[result.length];
+ }
+
+ /**
+ * Calculate the vector of acceptance intervals by running a scalar operation
+ * component-wise over a vector.
+ *
+ * This is used for situations where a component-wise operation, like vector
+ * negation, is needed as part of an inherited accuracy, but the top-level
+ * operation test don't require an explicit vector definition of the function,
+ * due to the generated 'vectorize' tests being sufficient.
+ *
+ * @param x input domain intervals vector
+ * @param op scalar operation to be run component-wise
+ * @returns a vector of intervals with the outputs of op.impl
+ */
+ runScalarToIntervalOpComponentWise(x, op) {
+ return this.toVector(x.map((e) => this.runScalarToIntervalOp(e, op)));
+ }
+
+ /**
+ * Calculate the vector of acceptance intervals for a vector function over
+ * given intervals
+ *
+ * @param x first input domain intervals vector
+ * @param y second input domain intervals vector
+ * @param op operation defining the function being run
+ * @returns a vector of spans over all the outputs of op.impl
+ */
+ runVectorPairToVectorOp(x, y, op) {
+ if (x.some((e) => !e.isFinite()) || y.some((e) => !e.isFinite())) {
+ return this.constants().unboundedVector[x.length];
+ }
+
+ const x_values = cartesianProduct(...x.map((e) => e.bounds()));
+ const y_values = cartesianProduct(...y.map((e) => e.bounds()));
+
+ const outputs = new Set();
+ x_values.forEach((inner_x) => {
+ y_values.forEach((inner_y) => {
+ outputs.add(this.roundAndFlushVectorPairToVector(inner_x, inner_y, op));
+ });
+ });
+
+ const result = this.spanVectors(...outputs);
+ return result.every((e) => e.isFinite()) ?
+ result :
+ this.constants().unboundedVector[result.length];
+ }
+
+ /**
+ * Calculate the vector of acceptance intervals by running a scalar operation
+ * component-wise over a pair of vectors.
+ *
+ * This is used for situations where a component-wise operation, like vector
+ * subtraction, is needed as part of an inherited accuracy, but the top-level
+ * operation test don't require an explicit vector definition of the function,
+ * due to the generated 'vectorize' tests being sufficient.
+ *
+ * @param x first input domain intervals vector
+ * @param y second input domain intervals vector
+ * @param op scalar operation to be run component-wise
+ * @returns a vector of intervals with the outputs of op.impl
+ */
+ runScalarPairToIntervalOpVectorComponentWise(
+ x,
+ y,
+ op)
+ {
+ assert(
+ x.length === y.length,
+ `runScalarPairToIntervalOpVectorComponentWise requires vectors of the same dimensions`
+ );
+
+ return this.toVector(
+ x.map((i, idx) => {
+ return this.runScalarPairToIntervalOp(i, y[idx], op);
+ })
+ );
+ }
+
+ /**
+ * Calculate the matrix of acceptance intervals for a pair of matrix function over
+ * given intervals
+ *
+ * @param m input domain intervals matrix
+ * @param op operation defining the function being run
+ * @returns a matrix of spans over all the outputs of op.impl
+ */
+ runMatrixToMatrixOp(m, op) {
+ 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];
+ }
+
+ const m_flat = flatten2DArray(m);
+ const m_values = cartesianProduct(...m_flat.map((e) => e.bounds()));
+
+ const outputs = new Set();
+ m_values.forEach((inner_m) => {
+ const unflat_m = unflatten2DArray(inner_m, num_cols, num_rows);
+ outputs.add(this.roundAndFlushMatrixToMatrix(unflat_m, op));
+ });
+
+ const result = this.spanMatrices(...outputs);
+ const result_cols = result.length;
+ const result_rows = result[0].length;
+
+ // FPMatrix has to be coerced to ROArrayArray<FPInterval> to use .every. This should
+ // always be safe, since FPMatrix are defined as fixed length array of
+ // arrays.
+ return result.every((c) => c.every((r) => r.isFinite())) ?
+ result :
+ this.constants().unboundedMatrix[result_cols][result_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.
+ *
+ * @param x first input domain intervals matrix
+ * @param y second input domain intervals matrix
+ * @param op scalar operation to be run component-wise
+ * @returns a matrix of intervals with the outputs of op.impl
+ */
+ runScalarPairToIntervalOpMatrixComponentWise(
+ x,
+ y,
+ op)
+ {
+ assert(
+ x.length === y.length && x[0].length === y[0].length,
+ `runScalarPairToIntervalOpMatrixComponentWise requires matrices of the same dimensions`
+ );
+
+ const cols = x.length;
+ const rows = x[0].length;
+ const flat_x = flatten2DArray(x);
+ const flat_y = flatten2DArray(y);
+
+ return this.toMatrix(
+ unflatten2DArray(
+ flat_x.map((i, idx) => {
+ return this.runScalarPairToIntervalOp(i, flat_y[idx], op);
+ }),
+ cols,
+ rows
+ )
+ );
+ }
+
+ // API - Fundamental Error Intervals
+
+ /** @returns a ScalarToIntervalOp for [n - error_range, n + error_range] */
+ AbsoluteErrorIntervalOp(error_range) {
+ const op = {
+ impl: (_) => {
+ return this.constants().unboundedInterval;
+ }
+ };
+
+ assert(
+ error_range >= 0,
+ `absoluteErrorInterval must have non-negative error range, get ${error_range}`
+ );
+
+ if (this.isFinite(error_range)) {
+ op.impl = (n) => {
+ assert(!Number.isNaN(n), `absolute error not defined for NaN`);
+ // Return anyInterval if given center n is infinity.
+ if (!this.isFinite(n)) {
+ return this.constants().unboundedInterval;
+ }
+ return this.toInterval([n - error_range, n + error_range]);
+ };
+ }
+
+ return op;
+ }
+
+ absoluteErrorIntervalImpl(n, error_range) {
+ error_range = Math.abs(error_range);
+ return this.runScalarToIntervalOp(
+ this.toInterval(n),
+ this.AbsoluteErrorIntervalOp(error_range)
+ );
+ }
+
+ /** @returns an interval of the absolute error around the point */
+
+
+ /**
+ * Defines a ScalarToIntervalOp for an interval of the correctly rounded values
+ * around the point
+ */
+ CorrectlyRoundedIntervalOp = {
+ impl: (n) => {
+ assert(!Number.isNaN(n), `absolute not defined for NaN`);
+ return this.toInterval(n);
+ }
+ };
+
+ correctlyRoundedIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.CorrectlyRoundedIntervalOp);
+ }
+
+ /** @returns an interval of the correctly rounded values around the point */
+
+
+ correctlyRoundedMatrixImpl(m) {
+ return this.toMatrix(map2DArray(m, this.correctlyRoundedInterval));
+ }
+
+ /** @returns a matrix of correctly rounded intervals for the provided matrix */
+
+
+ /** @returns a ScalarToIntervalOp for [n - numULP * ULP(n), n + numULP * ULP(n)] */
+ ULPIntervalOp(numULP) {
+ const op = {
+ impl: (_) => {
+ return this.constants().unboundedInterval;
+ }
+ };
+
+ if (this.isFinite(numULP)) {
+ op.impl = (n) => {
+ assert(!Number.isNaN(n), `ULP error not defined for NaN`);
+
+ const ulp = this.oneULP(n);
+ const begin = n - numULP * ulp;
+ const end = n + numULP * ulp;
+
+ return this.toInterval([
+ Math.min(begin, this.flushSubnormal(begin)),
+ Math.max(end, this.flushSubnormal(end))]
+ );
+ };
+ }
+
+ return op;
+ }
+
+ ulpIntervalImpl(n, numULP) {
+ numULP = Math.abs(numULP);
+ return this.runScalarToIntervalOp(this.toInterval(n), this.ULPIntervalOp(numULP));
+ }
+
+ /** @returns an interval of N * ULP around the point */
+
+
+ // API - Acceptance Intervals
+
+ AbsIntervalOp = {
+ impl: (n) => {
+ return this.correctlyRoundedInterval(Math.abs(n));
+ }
+ };
+
+ absIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.AbsIntervalOp);
+ }
+
+ /** Calculate an acceptance interval for abs(n) */
+
+
+ // This op is implemented differently for f32 and f16.
+ AcosIntervalOp = {
+ impl: this.limitScalarToIntervalDomain(this.toInterval([-1.0, 1.0]), (n) => {
+ 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)));
+ const approx_abs_error = this.kind === 'f32' ? 6.77e-5 : 3.91e-3;
+ return this.spanIntervals(
+ this.atan2Interval(y, n),
+ this.absoluteErrorInterval(Math.acos(n), approx_abs_error)
+ );
+ })
+ };
+
+ acosIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.AcosIntervalOp);
+ }
+
+ /** Calculate an acceptance interval for acos(n) */
+
+
+ AcoshAlternativeIntervalOp = {
+ impl: (x) => {
+ // acosh(x) = log(x + sqrt((x + 1.0f) * (x - 1.0)))
+ const inner_value = this.multiplicationInterval(
+ this.additionInterval(x, 1.0),
+ this.subtractionInterval(x, 1.0)
+ );
+ const sqrt_value = this.sqrtInterval(inner_value);
+ return this.logInterval(this.additionInterval(x, sqrt_value));
+ }
+ };
+
+ acoshAlternativeIntervalImpl(x) {
+ return this.runScalarToIntervalOp(this.toInterval(x), this.AcoshAlternativeIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of acosh(x) using log(x + sqrt((x + 1.0f) * (x - 1.0))) */
+
+
+ AcoshPrimaryIntervalOp = {
+ impl: (x) => {
+ // acosh(x) = log(x + sqrt(x * x - 1.0))
+ const inner_value = this.subtractionInterval(this.multiplicationInterval(x, x), 1.0);
+ const sqrt_value = this.sqrtInterval(inner_value);
+ return this.logInterval(this.additionInterval(x, sqrt_value));
+ }
+ };
+
+ acoshPrimaryIntervalImpl(x) {
+ return this.runScalarToIntervalOp(this.toInterval(x), this.AcoshPrimaryIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of acosh(x) using log(x + sqrt(x * x - 1.0)) */
+
+
+ /** All acceptance interval functions for acosh(x) */
+
+
+ AdditionIntervalOp = {
+ impl: (x, y) => {
+ return this.correctlyRoundedInterval(x + y);
+ }
+ };
+
+ additionIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.AdditionIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of x + y, when x and y are both scalars */
+
+
+
+
+
+ additionMatrixMatrixIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOpMatrixComponentWise(
+ this.toMatrix(x),
+ this.toMatrix(y),
+ this.AdditionIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of x + y, when x and y are matrices */
+
+
+
+
+
+ // This op is implemented differently for f32 and f16.
+ AsinIntervalOp = {
+ impl: this.limitScalarToIntervalDomain(this.toInterval([-1.0, 1.0]), (n) => {
+ 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;
+ return this.spanIntervals(
+ this.atan2Interval(n, x),
+ this.absoluteErrorInterval(Math.asin(n), approx_abs_error)
+ );
+ })
+ };
+
+ /** Calculate an acceptance interval for asin(n) */
+ asinIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.AsinIntervalOp);
+ }
+
+ /** Calculate an acceptance interval for asin(n) */
+
+
+ AsinhIntervalOp = {
+ impl: (x) => {
+ // asinh(x) = log(x + sqrt(x * x + 1.0))
+ const inner_value = this.additionInterval(this.multiplicationInterval(x, x), 1.0);
+ const sqrt_value = this.sqrtInterval(inner_value);
+ return this.logInterval(this.additionInterval(x, sqrt_value));
+ }
+ };
+
+ asinhIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.AsinhIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of asinh(x) */
+
+
+ AtanIntervalOp = {
+ impl: (n) => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const ulp_error = this.kind === 'f32' ? 4096 : 5;
+ return this.ulpInterval(Math.atan(n), ulp_error);
+ }
+ };
+
+ /** Calculate an acceptance interval of atan(x) */
+ atanIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.AtanIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of atan(x) */
+
+
+ // This op is implemented differently for f32 and f16.
+ Atan2IntervalOpBuilder() {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const constants = this.constants();
+ // For atan2, the params are labelled (y, x), not (x, y), so domain.x is first parameter (y),
+ // and domain.y is the second parameter (x).
+ // The first param must be finite and normal.
+ const domain_x = [
+ this.toInterval([constants.negative.min, constants.negative.max]),
+ this.toInterval([constants.positive.min, constants.positive.max])];
+
+ // inherited from division
+ const domain_y =
+ this.kind === 'f32' ?
+ [this.toInterval([-(2 ** 126), -(2 ** -126)]), this.toInterval([2 ** -126, 2 ** 126])] :
+ [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, x) => {
+ // 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);
+ }
+ ),
+ extrema: (y, x) => {
+ // There is discontinuity, which generates an unbounded result, at y/x = 0 that will dominate the accuracy
+ if (y.contains(0)) {
+ if (x.contains(0)) {
+ return [this.toInterval(0), this.toInterval(0)];
+ }
+ return [this.toInterval(0), x];
+ }
+ return [y, x];
+ }
+ };
+ }
+
+ atan2IntervalImpl(y, x) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(y),
+ this.toInterval(x),
+ this.Atan2IntervalOpBuilder()
+ );
+ }
+
+ /** Calculate an acceptance interval of atan2(y, x) */
+
+
+
+
+
+ AtanhIntervalOp = {
+ impl: (n) => {
+ // atanh(x) = log((1.0 + x) / (1.0 - x)) * 0.5
+ const numerator = this.additionInterval(1.0, n);
+ const denominator = this.subtractionInterval(1.0, n);
+ const log_interval = this.logInterval(this.divisionInterval(numerator, denominator));
+ return this.multiplicationInterval(log_interval, 0.5);
+ }
+ };
+
+ atanhIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.AtanhIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of atanh(x) */
+
+
+ CeilIntervalOp = {
+ impl: (n) => {
+ return this.correctlyRoundedInterval(Math.ceil(n));
+ }
+ };
+
+ ceilIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.CeilIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of ceil(x) */
+
+
+ ClampMedianIntervalOp = {
+ impl: (x, y, z) => {
+ return this.correctlyRoundedInterval(
+ // Default sort is string sort, so have to implement numeric comparison.
+ // Cannot use the b-a one-liner, because that assumes no infinities.
+ [x, y, z].sort((a, b) => {
+ if (a < b) {
+ return -1;
+ }
+ if (a > b) {
+ return 1;
+ }
+ return 0;
+ })[1]
+ );
+ }
+ };
+
+ clampMedianIntervalImpl(
+ x,
+ y,
+ z)
+ {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.toInterval(z),
+ this.ClampMedianIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of clamp(x, y, z) via median(x, y, z) */
+
+
+
+
+
+
+ ClampMinMaxIntervalOp = {
+ impl: (x, low, high) => {
+ return this.minInterval(this.maxInterval(x, low), high);
+ }
+ };
+
+ clampMinMaxIntervalImpl(
+ x,
+ low,
+ high)
+ {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(low),
+ this.toInterval(high),
+ this.ClampMinMaxIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of clamp(x, high, low) via min(max(x, low), high) */
+
+
+
+
+
+
+ /** All acceptance interval functions for clamp(x, y, z) */
+
+
+ CosIntervalOp = {
+ impl: this.limitScalarToIntervalDomain(
+ this.constants().negPiToPiInterval,
+ (n) => {
+ 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);
+ }
+ )
+ };
+
+ cosIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.CosIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of cos(x) */
+
+
+ CoshIntervalOp = {
+ impl: (n) => {
+ // cosh(x) = (exp(x) + exp(-x)) * 0.5
+ const minus_n = this.negationInterval(n);
+ return this.multiplicationInterval(
+ this.additionInterval(this.expInterval(n), this.expInterval(minus_n)),
+ 0.5
+ );
+ }
+ };
+
+ coshIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.CoshIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of cosh(x) */
+
+
+ CrossIntervalOp = {
+ impl: (x, y) => {
+ assert(x.length === 3, `CrossIntervalOp received x with ${x.length} instead of 3`);
+ assert(y.length === 3, `CrossIntervalOp received y with ${y.length} instead of 3`);
+
+ // cross(x, y) = r, where
+ // r[0] = x[1] * y[2] - x[2] * y[1]
+ // r[1] = x[2] * y[0] - x[0] * y[2]
+ // r[2] = x[0] * y[1] - x[1] * y[0]
+
+ const r0 = this.subtractionInterval(
+ this.multiplicationInterval(x[1], y[2]),
+ this.multiplicationInterval(x[2], y[1])
+ );
+ const r1 = this.subtractionInterval(
+ this.multiplicationInterval(x[2], y[0]),
+ this.multiplicationInterval(x[0], y[2])
+ );
+ const r2 = this.subtractionInterval(
+ this.multiplicationInterval(x[0], y[1]),
+ this.multiplicationInterval(x[1], y[0])
+ );
+ return [r0, r1, r2];
+ }
+ };
+
+ crossIntervalImpl(x, y) {
+ assert(x.length === 3, `Cross is only defined for vec3`);
+ assert(y.length === 3, `Cross is only defined for vec3`);
+ return this.runVectorPairToVectorOp(this.toVector(x), this.toVector(y), this.CrossIntervalOp);
+ }
+
+ /** Calculate a vector of acceptance intervals for cross(x, y) */
+
+
+ DegreesIntervalOp = {
+ impl: (n) => {
+ return this.multiplicationInterval(n, 57.295779513082322865);
+ }
+ };
+
+ degreesIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.DegreesIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of degrees(x) */
+
+
+ /**
+ * Calculate the minor of a NxN matrix.
+ *
+ * The ijth minor of a square matrix, is the N-1xN-1 matrix created by removing
+ * the ith column and jth row from the original matrix.
+ */
+ minorNxN(m, col, row) {
+ const dim = m.length;
+ assert(m.length === m[0].length, `minorMatrix is only defined for square matrices`);
+ assert(col >= 0 && col < dim, `col ${col} needs be in [0, # of columns '${dim}')`);
+ assert(row >= 0 && row < dim, `row ${row} needs be in [0, # of rows '${dim}')`);
+
+ const result = [...Array(dim - 1)].map((_) => [...Array(dim - 1)]);
+
+ const col_indices = [...Array(dim).keys()].filter((e) => e !== col);
+ const row_indices = [...Array(dim).keys()].filter((e) => e !== row);
+
+ col_indices.forEach((c, i) => {
+ row_indices.forEach((r, j) => {
+ result[i][j] = m[c][r];
+ });
+ });
+ return result;
+ }
+
+ /** Calculate an acceptance interval for determinant(m), where m is a 2x2 matrix */
+ determinant2x2Interval(m) {
+ assert(
+ m.length === m[0].length && m.length === 2,
+ `determinant2x2Interval called on non-2x2 matrix`
+ );
+ return this.subtractionInterval(
+ this.multiplicationInterval(m[0][0], m[1][1]),
+ this.multiplicationInterval(m[0][1], m[1][0])
+ );
+ }
+
+ /** Calculate an acceptance interval for determinant(m), where m is a 3x3 matrix */
+ determinant3x3Interval(m) {
+ assert(
+ m.length === m[0].length && m.length === 3,
+ `determinant3x3Interval called on non-3x3 matrix`
+ );
+
+ // M is a 3x3 matrix
+ // det(M) is A + B + C, where A, B, C are three elements in a row/column times
+ // their own co-factor.
+ // (The co-factor is the determinant of the minor of that position with the
+ // appropriate +/-)
+ // For simplicity sake A, B, C are calculated as the elements of the first
+ // column
+ const A = this.multiplicationInterval(
+ m[0][0],
+ this.determinant2x2Interval(this.minorNxN(m, 0, 0))
+ );
+ const B = this.multiplicationInterval(
+ -m[0][1],
+ this.determinant2x2Interval(this.minorNxN(m, 0, 1))
+ );
+ const C = this.multiplicationInterval(
+ m[0][2],
+ this.determinant2x2Interval(this.minorNxN(m, 0, 2))
+ );
+
+ // Need to calculate permutations, since for fp addition is not associative,
+ // so A + B + C is not guaranteed to equal B + C + A, etc.
+ const permutations = calculatePermutations([A, B, C]);
+ return this.spanIntervals(
+ ...permutations.map((p) =>
+ p.reduce((prev, cur) => this.additionInterval(prev, cur))
+ )
+ );
+ }
+
+ /** Calculate an acceptance interval for determinant(m), where m is a 4x4 matrix */
+ determinant4x4Interval(m) {
+ assert(
+ m.length === m[0].length && m.length === 4,
+ `determinant3x3Interval called on non-4x4 matrix`
+ );
+
+ // M is a 4x4 matrix
+ // det(M) is A + B + C + D, where A, B, C, D are four elements in a row/column
+ // times their own co-factor.
+ // (The co-factor is the determinant of the minor of that position with the
+ // appropriate +/-)
+ // For simplicity sake A, B, C, D are calculated as the elements of the
+ // first column
+ const A = this.multiplicationInterval(
+ m[0][0],
+ this.determinant3x3Interval(this.minorNxN(m, 0, 0))
+ );
+ const B = this.multiplicationInterval(
+ -m[0][1],
+ this.determinant3x3Interval(this.minorNxN(m, 0, 1))
+ );
+ const C = this.multiplicationInterval(
+ m[0][2],
+ this.determinant3x3Interval(this.minorNxN(m, 0, 2))
+ );
+ const D = this.multiplicationInterval(
+ -m[0][3],
+ this.determinant3x3Interval(this.minorNxN(m, 0, 3))
+ );
+
+ // Need to calculate permutations, since for fp addition is not associative
+ // so A + B + C + D is not guaranteed to equal B + C + A + D, etc.
+ const permutations = calculatePermutations([A, B, C, D]);
+ return this.spanIntervals(
+ ...permutations.map((p) =>
+ p.reduce((prev, cur) => this.additionInterval(prev, cur))
+ )
+ );
+ }
+
+ /**
+ * This code calculates 3x3 and 4x4 determinants using the textbook co-factor
+ * method, using the first column for the co-factor selection.
+ *
+ * For matrices composed of integer elements, e, with |e|^4 < 2**21, this
+ * should be fine.
+ *
+ * For e, where e is subnormal or 4*(e^4) might not be precisely expressible as
+ * a f32 values, this approach breaks down, because the rule of all co-factor
+ * definitions of determinant being equal doesn't hold in these cases.
+ *
+ * The general solution for this is to calculate all the permutations of the
+ * operations in the worked out formula for determinant.
+ * For 3x3 this is tractable, but for 4x4 this works out to ~23! permutations
+ * that need to be calculated.
+ * Thus, CTS testing and the spec definition of accuracy is restricted to the
+ * space that the simple implementation is valid.
+ */
+ determinantIntervalImpl(x) {
+ const dim = x.length;
+ assert(
+ x[0].length === dim && (dim === 2 || dim === 3 || dim === 4),
+ `determinantInterval only defined for 2x2, 3x3 and 4x4 matrices`
+ );
+ switch (dim) {
+ case 2:
+ return this.determinant2x2Interval(x);
+ case 3:
+ return this.determinant3x3Interval(x);
+ case 4:
+ return this.determinant4x4Interval(x);
+ }
+ unreachable(
+ "determinantInterval called on x, where which has an unexpected dimension of '${dim}'"
+ );
+ }
+
+ /** Calculate an acceptance interval for determinant(x) */
+
+
+ DistanceIntervalScalarOp = {
+ impl: (x, y) => {
+ return this.lengthInterval(this.subtractionInterval(x, y));
+ }
+ };
+
+ DistanceIntervalVectorOp = {
+ impl: (x, y) => {
+ return this.lengthInterval(
+ this.runScalarPairToIntervalOpVectorComponentWise(
+ this.toVector(x),
+ this.toVector(y),
+ this.SubtractionIntervalOp
+ )
+ );
+ }
+ };
+
+ distanceIntervalImpl(
+ x,
+ y)
+ {
+ if (x instanceof Array && y instanceof Array) {
+ assert(
+ x.length === y.length,
+ `distanceInterval requires both params to have the same number of elements`
+ );
+ return this.runVectorPairToIntervalOp(
+ this.toVector(x),
+ this.toVector(y),
+ this.DistanceIntervalVectorOp
+ );
+ } else if (!(x instanceof Array) && !(y instanceof Array)) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.DistanceIntervalScalarOp
+ );
+ }
+ unreachable(
+ `distanceInterval requires both params to both the same type, either scalars or vectors`
+ );
+ }
+
+ /** Calculate an acceptance interval of distance(x, y) */
+
+
+
+
+
+ // This op is implemented differently for f32 and f16.
+ DivisionIntervalOpBuilder() {
+ const constants = this.constants();
+ const domain_x = [this.toInterval([constants.negative.min, constants.positive.max])];
+ const domain_y =
+ this.kind === 'f32' || this.kind === 'abstract' ?
+ [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, y) => {
+ if (y === 0) {
+ return constants.unboundedInterval;
+ }
+ return this.ulpInterval(x / y, 2.5);
+ }
+ ),
+ extrema: (x, y) => {
+ // division has a discontinuity at y = 0.
+ if (y.contains(0)) {
+ y = this.toInterval(0);
+ }
+ return [x, y];
+ }
+ };
+ }
+
+ divisionIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.DivisionIntervalOpBuilder()
+ );
+ }
+
+ /** Calculate an acceptance interval of x / y */
+
+
+
+
+
+ DotIntervalOp = {
+ impl: (x, y) => {
+ // dot(x, y) = sum of x[i] * y[i]
+ const multiplications = this.runScalarPairToIntervalOpVectorComponentWise(
+ this.toVector(x),
+ this.toVector(y),
+ this.MultiplicationIntervalOp
+ );
+
+ // vec2 doesn't require permutations, since a + b = b + a for floats
+ if (multiplications.length === 2) {
+ return this.additionInterval(multiplications[0], multiplications[1]);
+ }
+
+ // The spec does not state the ordering of summation, so all the
+ // permutations are calculated and their results spanned, since addition
+ // of more than two floats is not transitive, i.e. a + b + c is not
+ // guaranteed to equal b + a + c
+ const permutations = calculatePermutations(multiplications);
+ return this.spanIntervals(
+ ...permutations.map((p) => p.reduce((prev, cur) => this.additionInterval(prev, cur)))
+ );
+ }
+ };
+
+ dotIntervalImpl(
+ x,
+ y)
+ {
+ assert(x.length === y.length, `dot not defined for vectors with different lengths`);
+ return this.runVectorPairToIntervalOp(this.toVector(x), this.toVector(y), this.DotIntervalOp);
+ }
+
+ /** Calculated the acceptance interval for dot(x, y) */
+
+
+
+
+
+ ExpIntervalOp = {
+ impl: (n) => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const ulp_error = this.kind === 'f32' ? 3 + 2 * Math.abs(n) : 1 + 2 * Math.abs(n);
+ return this.ulpInterval(Math.exp(n), ulp_error);
+ }
+ };
+
+ expIntervalImpl(x) {
+ return this.runScalarToIntervalOp(this.toInterval(x), this.ExpIntervalOp);
+ }
+
+ /** Calculate an acceptance interval for exp(x) */
+
+
+ Exp2IntervalOp = {
+ impl: (n) => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const ulp_error = this.kind === 'f32' ? 3 + 2 * Math.abs(n) : 1 + 2 * Math.abs(n);
+ return this.ulpInterval(Math.pow(2, n), ulp_error);
+ }
+ };
+
+ exp2IntervalImpl(x) {
+ return this.runScalarToIntervalOp(this.toInterval(x), this.Exp2IntervalOp);
+ }
+
+ /** Calculate an acceptance interval for exp2(x) */
+
+
+ /**
+ * faceForward(x, y, z) = select(-x, x, dot(z, y) < 0.0)
+ *
+ * This builtin selects from two discrete results (delta rounding/flushing),
+ * so the majority of the framework code is not appropriate, since the
+ * framework attempts to span results.
+ *
+ * Thus, a bespoke implementation is used instead of
+ * defining an Op and running that through the framework.
+ */
+ faceForwardIntervalsImpl(
+ x,
+ y,
+ z)
+ {
+ const x_vec = this.toVector(x);
+ // Running vector through this.runScalarToIntervalOpComponentWise to make
+ // sure that flushing/rounding is handled, since toVector does not perform
+ // those operations.
+ const positive_x = this.runScalarToIntervalOpComponentWise(x_vec, {
+ impl: (i) => {
+ return this.toInterval(i);
+ }
+ });
+ const negative_x = this.runScalarToIntervalOpComponentWise(x_vec, this.NegationIntervalOp);
+
+ const dot_interval = this.dotInterval(z, y);
+
+ const results = [];
+
+ if (!dot_interval.isFinite()) {
+ // dot calculation went out of bounds
+ // Inserting undefined in the result, so that the test running framework
+ // is aware of this potential OOB.
+ // For const-eval tests, it means that the test case should be skipped,
+ // since the shader will fail to compile.
+ // For non-const-eval the undefined should be stripped out of the possible
+ // results.
+
+ results.push(undefined);
+ }
+
+ // Because the result of dot can be an interval, it might span across 0, thus
+ // it is possible that both -x and x are valid responses.
+ if (dot_interval.begin < 0 || dot_interval.end < 0) {
+ results.push(positive_x);
+ }
+
+ if (dot_interval.begin >= 0 || dot_interval.end >= 0) {
+ results.push(negative_x);
+ }
+
+ assert(
+ results.length > 0 || results.every((r) => r === undefined),
+ `faceForwardInterval selected neither positive x or negative x for the result, this shouldn't be possible`
+ );
+ return results;
+ }
+
+ /** Calculate the acceptance intervals for faceForward(x, y, z) */
+
+
+
+
+
+
+ FloorIntervalOp = {
+ impl: (n) => {
+ return this.correctlyRoundedInterval(Math.floor(n));
+ }
+ };
+
+ floorIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.FloorIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of floor(x) */
+
+
+ FmaIntervalOp = {
+ impl: (x, y, z) => {
+ return this.additionInterval(this.multiplicationInterval(x, y), z);
+ }
+ };
+
+ fmaIntervalImpl(x, y, z) {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.toInterval(z),
+ this.FmaIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval for fma(x, y, z) */
+
+
+ FractIntervalOp = {
+ impl: (n) => {
+ // fract(x) = x - floor(x) is defined in the spec.
+ // For people coming from a non-graphics background this will cause some
+ // unintuitive results. For example,
+ // fract(-1.1) is not 0.1 or -0.1, but instead 0.9.
+ // This is how other shading languages operate and allows for a desirable
+ // wrap around in graphics programming.
+ const result = this.subtractionInterval(n, this.floorInterval(n));
+ assert(
+ // negative.subnormal.min instead of 0, because FTZ can occur
+ // selectively during the calculation
+ this.toInterval([this.constants().negative.subnormal.min, 1.0]).contains(result),
+ `fract(${n}) interval [${result}] unexpectedly extends beyond [~0.0, 1.0]`
+ );
+ if (result.contains(1)) {
+ // Very small negative numbers can lead to catastrophic cancellation,
+ // thus calculating a fract of 1.0, which is technically not a
+ // fractional part, so some implementations clamp the result to next
+ // nearest number.
+ return this.spanIntervals(result, this.toInterval(this.constants().positive.less_than_one));
+ }
+ return result;
+ }
+ };
+
+ fractIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.FractIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of fract(x) */
+
+
+ InverseSqrtIntervalOp = {
+ impl: this.limitScalarToIntervalDomain(
+ this.constants().greaterThanZeroInterval,
+ (n) => {
+ return this.ulpInterval(1 / Math.sqrt(n), 2);
+ }
+ )
+ };
+
+ inverseSqrtIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.InverseSqrtIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of inverseSqrt(x) */
+
+
+ LdexpIntervalOp = {
+ impl: (e1, e2) => {
+ 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) {
+ 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.
+ // 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
+ return this.constants().unboundedInterval;
+ }
+ // The result may be zero if e2 + bias <= 0, but we can't simply span the interval to 0.0.
+ // For example, for f32 input e1 = 2**120 and e2 = -130, e2 + bias = -3 <= 0, but
+ // e1 * 2 ** e2 = 2**-10, so the valid result is 2**-10 or 0.0, instead of [0.0, 2**-10].
+ // Always return the correctly-rounded interval, and special examination should be taken when
+ // using the result.
+ return this.correctlyRoundedInterval(result);
+ }
+ };
+
+ ldexpIntervalImpl(e1, e2) {
+ // Only round and flush e1, as e2 is of integer type (i32 or abstract integer) and should be
+ // precise.
+ return this.roundAndFlushScalarToInterval(e1, {
+ impl: (e1) => this.LdexpIntervalOp.impl(e1, e2)
+ });
+ }
+
+ /**
+ * Calculate an acceptance interval of ldexp(e1, e2), where e2 is integer
+ *
+ * Spec indicate that the result may be zero if e2 + bias <= 0, no matter how large
+ * was e1 * 2 ** e2, i.e. the actual valid result is correctlyRounded(e1 * 2 ** e2) or 0.0, if
+ * e2 + bias <= 0. Such discontinious flush-to-zero behavior is hard to be expressed using
+ * FPInterval, therefore in the situation of e2 + bias <= 0 the returned interval would be just
+ * correctlyRounded(e1 * 2 ** e2), and special examination should be taken when using the result.
+ *
+ */
+
+
+ LengthIntervalScalarOp = {
+ impl: (n) => {
+ return this.sqrtInterval(this.multiplicationInterval(n, n));
+ }
+ };
+
+ LengthIntervalVectorOp = {
+ impl: (n) => {
+ return this.sqrtInterval(this.dotInterval(n, n));
+ }
+ };
+
+ lengthIntervalImpl(n) {
+ if (n instanceof Array) {
+ return this.runVectorToIntervalOp(this.toVector(n), this.LengthIntervalVectorOp);
+ } else {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.LengthIntervalScalarOp);
+ }
+ }
+
+ /** Calculate an acceptance interval of length(x) */
+
+
+
+
+ LogIntervalOp = {
+ impl: this.limitScalarToIntervalDomain(
+ this.constants().greaterThanZeroInterval,
+ (n) => {
+ 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);
+ }
+ )
+ };
+
+ logIntervalImpl(x) {
+ return this.runScalarToIntervalOp(this.toInterval(x), this.LogIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of log(x) */
+
+
+ Log2IntervalOp = {
+ impl: this.limitScalarToIntervalDomain(
+ this.constants().greaterThanZeroInterval,
+ (n) => {
+ 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);
+ }
+ )
+ };
+
+ log2IntervalImpl(x) {
+ return this.runScalarToIntervalOp(this.toInterval(x), this.Log2IntervalOp);
+ }
+
+ /** Calculate an acceptance interval of log2(x) */
+
+
+ MaxIntervalOp = {
+ impl: (x, y) => {
+ // If both of the inputs are subnormal, then either of the inputs can be returned
+ if (this.isSubnormal(x) && this.isSubnormal(y)) {
+ return this.correctlyRoundedInterval(
+ this.spanIntervals(this.toInterval(x), this.toInterval(y))
+ );
+ }
+
+ return this.correctlyRoundedInterval(Math.max(x, y));
+ }
+ };
+
+ maxIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.MaxIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of max(x, y) */
+
+
+
+
+
+ MinIntervalOp = {
+ impl: (x, y) => {
+ // If both of the inputs are subnormal, then either of the inputs can be returned
+ if (this.isSubnormal(x) && this.isSubnormal(y)) {
+ return this.correctlyRoundedInterval(
+ this.spanIntervals(this.toInterval(x), this.toInterval(y))
+ );
+ }
+
+ return this.correctlyRoundedInterval(Math.min(x, y));
+ }
+ };
+
+ minIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.MinIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of min(x, y) */
+
+
+
+
+
+ MixImpreciseIntervalOp = {
+ impl: (x, y, z) => {
+ // x + (y - x) * z =
+ // x + t, where t = (y - x) * z
+ const t = this.multiplicationInterval(this.subtractionInterval(y, x), z);
+ return this.additionInterval(x, t);
+ }
+ };
+
+ mixImpreciseIntervalImpl(x, y, z) {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.toInterval(z),
+ this.MixImpreciseIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of mix(x, y, z) using x + (y - x) * z */
+
+
+ MixPreciseIntervalOp = {
+ impl: (x, y, z) => {
+ // x * (1.0 - z) + y * z =
+ // t + s, where t = x * (1.0 - z), s = y * z
+ const t = this.multiplicationInterval(x, this.subtractionInterval(1.0, z));
+ const s = this.multiplicationInterval(y, z);
+ return this.additionInterval(t, s);
+ }
+ };
+
+ mixPreciseIntervalImpl(x, y, z) {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.toInterval(z),
+ this.MixPreciseIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of mix(x, y, z) using x * (1.0 - z) + y * z */
+
+
+ /** All acceptance interval functions for mix(x, y, z) */
+
+
+ modfIntervalImpl(n) {
+ const fract = this.correctlyRoundedInterval(n % 1.0);
+ const whole = this.correctlyRoundedInterval(n - n % 1.0);
+ return { fract, whole };
+ }
+
+ /** Calculate an acceptance interval of modf(x) */
+
+
+ MultiplicationInnerOp = {
+ impl: (x, y) => {
+ return this.correctlyRoundedInterval(x * y);
+ }
+ };
+
+ MultiplicationIntervalOp = {
+ impl: (x, y) => {
+ return this.roundAndFlushScalarPairToInterval(x, y, this.MultiplicationInnerOp);
+ }
+ };
+
+ multiplicationIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.MultiplicationIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of x * y */
+
+
+
+
+
+ /**
+ * @returns the vector result of multiplying the given vector by the given
+ * scalar
+ */
+ multiplyVectorByScalar(v, c) {
+ return this.toVector(v.map((x) => this.multiplicationInterval(x, c)));
+ }
+
+ multiplicationMatrixScalarIntervalImpl(mat, scalar) {
+ const cols = mat.length;
+ const rows = mat[0].length;
+ return this.toMatrix(
+ unflatten2DArray(
+ flatten2DArray(mat).map((e) => this.multiplicationInterval(e, scalar)),
+ cols,
+ rows
+ )
+ );
+ }
+
+ /** Calculate an acceptance interval of x * y, when x is a matrix and y is a scalar */
+
+
+
+
+
+ multiplicationScalarMatrixIntervalImpl(scalar, mat) {
+ return this.multiplicationMatrixScalarIntervalImpl(mat, scalar);
+ }
+
+ /** Calculate an acceptance interval of x * y, when x is a scalar and y is a matrix */
+
+
+
+
+
+ multiplicationMatrixMatrixIntervalImpl(
+ mat_x,
+ mat_y)
+ {
+ const x_cols = mat_x.length;
+ const x_rows = mat_x[0].length;
+ const y_cols = mat_y.length;
+ const y_rows = mat_y[0].length;
+ assert(x_cols === y_rows, `'mat${x_cols}x${x_rows} * mat${y_cols}x${y_rows}' is not defined`);
+
+ const x_transposed = this.transposeInterval(mat_x);
+
+ const result = [...Array(y_cols)].map((_) => [...Array(x_rows)]);
+ mat_y.forEach((y, i) => {
+ x_transposed.forEach((x, j) => {
+ result[i][j] = this.dotInterval(x, y);
+ });
+ });
+
+ return result;
+ }
+
+ /** Calculate an acceptance interval of x * y, when x is a matrix and y is a matrix */
+
+
+
+
+
+ multiplicationMatrixVectorIntervalImpl(
+ x,
+ y)
+ {
+ const cols = x.length;
+ const rows = x[0].length;
+ assert(y.length === cols, `'mat${cols}x${rows} * vec${y.length}' is not defined`);
+
+ return this.transposeInterval(x).map((e) => this.dotInterval(e, y));
+ }
+
+ /** Calculate an acceptance interval of x * y, when x is a matrix and y is a vector */
+
+
+
+
+
+ multiplicationVectorMatrixIntervalImpl(
+ x,
+ y)
+ {
+ const cols = y.length;
+ const rows = y[0].length;
+ assert(x.length === rows, `'vec${x.length} * mat${cols}x${rows}' is not defined`);
+
+ return y.map((e) => this.dotInterval(x, e));
+ }
+
+ /** Calculate an acceptance interval of x * y, when x is a vector and y is a matrix */
+
+
+
+
+
+ NegationIntervalOp = {
+ impl: (n) => {
+ return this.correctlyRoundedInterval(-n);
+ }
+ };
+
+ negationIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.NegationIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of -x */
+
+
+ NormalizeIntervalOp = {
+ impl: (n) => {
+ const length = this.lengthInterval(n);
+ return this.toVector(n.map((e) => this.divisionInterval(e, length)));
+ }
+ };
+
+ normalizeIntervalImpl(n) {
+ return this.runVectorToVectorOp(this.toVector(n), this.NormalizeIntervalOp);
+ }
+
+
+
+ PowIntervalOp = {
+ // pow(x, y) has no explicit domain restrictions, but inherits the x <= 0
+ // domain restriction from log2(x). Invoking log2Interval(x) in impl will
+ // enforce this, so there is no need to wrap the impl call here.
+ impl: (x, y) => {
+ return this.exp2Interval(this.multiplicationInterval(y, this.log2Interval(x)));
+ }
+ };
+
+ powIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.PowIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of pow(x, y) */
+
+
+
+
+
+ RadiansIntervalOp = {
+ impl: (n) => {
+ return this.multiplicationInterval(n, 0.017453292519943295474);
+ }
+ };
+
+ radiansIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.RadiansIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of radians(x) */
+
+
+ ReflectIntervalOp = {
+ impl: (x, y) => {
+ assert(
+ x.length === y.length,
+ `ReflectIntervalOp received x (${x}) and y (${y}) with different numbers of elements`
+ );
+
+ // reflect(x, y) = x - 2.0 * dot(x, y) * y
+ // = x - t * y, t = 2.0 * dot(x, y)
+ // x = incident vector
+ // y = normal of reflecting surface
+ const t = this.multiplicationInterval(2.0, this.dotInterval(x, y));
+ const rhs = this.multiplyVectorByScalar(y, t);
+ return this.runScalarPairToIntervalOpVectorComponentWise(
+ this.toVector(x),
+ rhs,
+ this.SubtractionIntervalOp
+ );
+ }
+ };
+
+ reflectIntervalImpl(x, y) {
+ assert(
+ x.length === y.length,
+ `reflect is only defined for vectors with the same number of elements`
+ );
+ return this.runVectorPairToVectorOp(this.toVector(x), this.toVector(y), this.ReflectIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of reflect(x, y) */
+
+
+
+
+
+ /**
+ * refract is a singular function in the sense that it is the only builtin that
+ * takes in (FPVector, FPVector, F32/F16) and returns FPVector and is basically
+ * defined in terms of other functions.
+ *
+ * Instead of implementing all the framework code to integrate it with its
+ * own operation type, etc, it instead has a bespoke implementation that is a
+ * composition of other builtin functions that use the framework.
+ */
+ refractIntervalImpl(i, s, r) {
+ assert(
+ i.length === s.length,
+ `refract is only defined for vectors with the same number of elements`
+ );
+
+ const r_squared = this.multiplicationInterval(r, r);
+ const dot = this.dotInterval(s, i);
+ const dot_squared = this.multiplicationInterval(dot, dot);
+ const one_minus_dot_squared = this.subtractionInterval(1, dot_squared);
+ const k = this.subtractionInterval(
+ 1.0,
+ this.multiplicationInterval(r_squared, one_minus_dot_squared)
+ );
+
+ if (!k.isFinite() || k.containsZeroOrSubnormals()) {
+ // There is a discontinuity at k == 0, due to sqrt(k) being calculated, so exiting early
+ return this.constants().unboundedVector[this.toVector(i).length];
+ }
+
+ if (k.end < 0.0) {
+ // if k is negative, then the zero vector is the valid response
+ return this.constants().zeroVector[this.toVector(i).length];
+ }
+
+ const dot_times_r = this.multiplicationInterval(dot, r);
+ 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(
+ this.multiplyVectorByScalar(i, r),
+ this.multiplyVectorByScalar(s, t),
+ this.SubtractionIntervalOp
+ ); // (i * r) - (s * t)
+ }
+
+ /** Calculate acceptance interval vectors of reflect(i, s, r) */
+
+
+
+
+
+
+ RemainderIntervalOp = {
+ impl: (x, y) => {
+ // x % y = x - y * trunc(x/y)
+ return this.subtractionInterval(
+ x,
+ this.multiplicationInterval(y, this.truncInterval(this.divisionInterval(x, y)))
+ );
+ }
+ };
+
+ /** Calculate an acceptance interval for x % y */
+ remainderIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.RemainderIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval for x % y */
+
+
+ RoundIntervalOp = {
+ impl: (n) => {
+ const k = Math.floor(n);
+ const diff_before = n - k;
+ const diff_after = k + 1 - n;
+ if (diff_before < diff_after) {
+ return this.correctlyRoundedInterval(k);
+ } else if (diff_before > diff_after) {
+ return this.correctlyRoundedInterval(k + 1);
+ }
+
+ // n is in the middle of two integers.
+ // The tie breaking rule is 'k if k is even, k + 1 if k is odd'
+ if (k % 2 === 0) {
+ return this.correctlyRoundedInterval(k);
+ }
+ return this.correctlyRoundedInterval(k + 1);
+ }
+ };
+
+ roundIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.RoundIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of round(x) */
+
+
+ /**
+ * The definition of saturate does not specify which version of clamp to use.
+ * Using min-max here, since it has wider acceptance intervals, that include
+ * all of median's.
+ */
+ saturateIntervalImpl(n) {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(n),
+ this.toInterval(0.0),
+ this.toInterval(1.0),
+ this.ClampMinMaxIntervalOp
+ );
+ }
+
+ /*** Calculate an acceptance interval of saturate(n) as clamp(n, 0.0, 1.0) */
+
+
+ SignIntervalOp = {
+ impl: (n) => {
+ if (n > 0.0) {
+ return this.correctlyRoundedInterval(1.0);
+ }
+ if (n < 0.0) {
+ return this.correctlyRoundedInterval(-1.0);
+ }
+
+ return this.correctlyRoundedInterval(0.0);
+ }
+ };
+
+ signIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.SignIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of sign(x) */
+
+
+ SinIntervalOp = {
+ impl: this.limitScalarToIntervalDomain(
+ this.constants().negPiToPiInterval,
+ (n) => {
+ 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);
+ }
+ )
+ };
+
+ sinIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.SinIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of sin(x) */
+
+
+ SinhIntervalOp = {
+ impl: (n) => {
+ // sinh(x) = (exp(x) - exp(-x)) * 0.5
+ const minus_n = this.negationInterval(n);
+ return this.multiplicationInterval(
+ this.subtractionInterval(this.expInterval(n), this.expInterval(minus_n)),
+ 0.5
+ );
+ }
+ };
+
+ sinhIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.SinhIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of sinh(x) */
+
+
+ SmoothStepOp = {
+ impl: (low, high, x) => {
+ // For clamp(foo, 0.0, 1.0) the different implementations of clamp provide
+ // the same value, so arbitrarily picking the minmax version to use.
+ // t = clamp((x - low) / (high - low), 0.0, 1.0)
+
+ const t = this.clampMedianInterval(
+ this.divisionInterval(
+ this.subtractionInterval(x, low),
+ this.subtractionInterval(high, low)),
+ 0.0,
+ 1.0);
+ // Inherited from t * t * (3.0 - 2.0 * t)
+
+ return this.multiplicationInterval(
+ t,
+ this.multiplicationInterval(t,
+ this.subtractionInterval(3.0,
+ this.multiplicationInterval(2.0, t))));
+ }
+ };
+
+ smoothStepIntervalImpl(low, high, x) {
+ return this.runScalarTripleToIntervalOp(
+ this.toInterval(low),
+ this.toInterval(high),
+ this.toInterval(x),
+ this.SmoothStepOp
+ );
+ }
+
+ /** Calculate an acceptance interval of smoothStep(low, high, x) */
+
+
+ SqrtIntervalOp = {
+ impl: (n) => {
+ return this.divisionInterval(1.0, this.inverseSqrtInterval(n));
+ }
+ };
+
+ sqrtIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.SqrtIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of sqrt(x) */
+
+
+ StepIntervalOp = {
+ impl: (edge, x) => {
+ if (edge <= x) {
+ return this.correctlyRoundedInterval(1.0);
+ }
+ return this.correctlyRoundedInterval(0.0);
+ }
+ };
+
+ stepIntervalImpl(edge, x) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(edge),
+ this.toInterval(x),
+ this.StepIntervalOp
+ );
+ }
+
+ /**
+ * Calculate an acceptance 'interval' for step(edge, x)
+ *
+ * step only returns two possible values, so its interval requires special
+ * interpretation in CTS tests.
+ * This interval will be one of four values: [0, 0], [0, 1], [1, 1] & [-∞, +∞].
+ * [0, 0] and [1, 1] indicate that the correct answer in point they encapsulate.
+ * [0, 1] should not be treated as a span, i.e. 0.1 is acceptable, but instead
+ * indicate either 0.0 or 1.0 are acceptable answers.
+ * [-∞, +∞] is treated as unbounded interval, since an unbounded or
+ * infinite value was passed in.
+ */
+
+
+ SubtractionIntervalOp = {
+ impl: (x, y) => {
+ return this.correctlyRoundedInterval(x - y);
+ }
+ };
+
+ subtractionIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOp(
+ this.toInterval(x),
+ this.toInterval(y),
+ this.SubtractionIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of x - y */
+
+
+
+
+
+ subtractionMatrixMatrixIntervalImpl(x, y) {
+ return this.runScalarPairToIntervalOpMatrixComponentWise(
+ this.toMatrix(x),
+ this.toMatrix(y),
+ this.SubtractionIntervalOp
+ );
+ }
+
+ /** Calculate an acceptance interval of x - y, when x and y are matrices */
+
+
+
+
+
+ TanIntervalOp = {
+ impl: (n) => {
+ return this.divisionInterval(this.sinInterval(n), this.cosInterval(n));
+ }
+ };
+
+ tanIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.TanIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of tan(x) */
+
+
+ TanhIntervalOp = {
+ impl: (n) => {
+ return this.divisionInterval(this.sinhInterval(n), this.coshInterval(n));
+ }
+ };
+
+ tanhIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.TanhIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of tanh(x) */
+
+
+ TransposeIntervalOp = {
+ impl: (m) => {
+ const num_cols = m.length;
+ const num_rows = m[0].length;
+ const result = [...Array(num_rows)].map((_) => [...Array(num_cols)]);
+
+ for (let i = 0; i < num_cols; i++) {
+ for (let j = 0; j < num_rows; j++) {
+ result[j][i] = this.correctlyRoundedInterval(m[i][j]);
+ }
+ }
+ return this.toMatrix(result);
+ }
+ };
+
+ transposeIntervalImpl(m) {
+ return this.runMatrixToMatrixOp(this.toMatrix(m), this.TransposeIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of transpose(m) */
+
+
+ TruncIntervalOp = {
+ impl: (n) => {
+ return this.correctlyRoundedInterval(Math.trunc(n));
+ }
+ };
+
+ truncIntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.TruncIntervalOp);
+ }
+
+ /** Calculate an acceptance interval of trunc(x) */
+
+}
+
+// Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this
+// executes before they are defined.
+const kF32UnboundedInterval = new FPInterval(
+ 'f32',
+ Number.NEGATIVE_INFINITY,
+ Number.POSITIVE_INFINITY
+);
+const kF32ZeroInterval = new FPInterval('f32', 0);
+
+class F32Traits extends FPTraits {
+ static _constants = {
+ positive: {
+ min: kValue.f32.positive.min,
+ max: kValue.f32.positive.max,
+ infinity: kValue.f32.positive.infinity,
+ nearest_max: kValue.f32.positive.nearest_max,
+ less_than_one: kValue.f32.positive.less_than_one,
+ subnormal: {
+ min: kValue.f32.positive.subnormal.min,
+ max: kValue.f32.positive.subnormal.max
+ },
+ pi: {
+ whole: kValue.f32.positive.pi.whole,
+ three_quarters: kValue.f32.positive.pi.three_quarters,
+ half: kValue.f32.positive.pi.half,
+ third: kValue.f32.positive.pi.third,
+ quarter: kValue.f32.positive.pi.quarter,
+ sixth: kValue.f32.positive.pi.sixth
+ },
+ e: kValue.f32.positive.e
+ },
+ negative: {
+ min: kValue.f32.negative.min,
+ max: kValue.f32.negative.max,
+ infinity: kValue.f32.negative.infinity,
+ nearest_min: kValue.f32.negative.nearest_min,
+ less_than_one: kValue.f32.negative.less_than_one,
+ subnormal: {
+ min: kValue.f32.negative.subnormal.min,
+ max: kValue.f32.negative.subnormal.max
+ },
+ pi: {
+ whole: kValue.f32.negative.pi.whole,
+ three_quarters: kValue.f32.negative.pi.three_quarters,
+ half: kValue.f32.negative.pi.half,
+ third: kValue.f32.negative.pi.third,
+ quarter: kValue.f32.negative.pi.quarter,
+ sixth: kValue.f32.negative.pi.sixth
+ }
+ },
+ unboundedInterval: kF32UnboundedInterval,
+ zeroInterval: kF32ZeroInterval,
+ // Have to use the constants.ts values here, because values defined in the
+ // initializer cannot be referenced in the initializer
+ negPiToPiInterval: new FPInterval(
+ 'f32',
+ kValue.f32.negative.pi.whole,
+ kValue.f32.positive.pi.whole
+ ),
+ greaterThanZeroInterval: new FPInterval(
+ 'f32',
+ kValue.f32.positive.subnormal.min,
+ kValue.f32.positive.max
+ ),
+ zeroVector: {
+ 2: [kF32ZeroInterval, kF32ZeroInterval],
+ 3: [kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval],
+ 4: [kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval]
+ },
+ unboundedVector: {
+ 2: [kF32UnboundedInterval, kF32UnboundedInterval],
+ 3: [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ 4: [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval]
+
+ },
+ unboundedMatrix: {
+ 2: {
+ 2: [
+ [kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval]],
+
+ 3: [
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval]],
+
+ 4: [
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval],
+
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval]]
+
+
+ },
+ 3: {
+ 2: [
+ [kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval]],
+
+ 3: [
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval]],
+
+ 4: [
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval],
+
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval],
+
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval]]
+
+
+ },
+ 4: {
+ 2: [
+ [kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval]],
+
+ 3: [
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval],
+ [kF32UnboundedInterval, kF32UnboundedInterval, kF32UnboundedInterval]],
+
+ 4: [
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval],
+
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval],
+
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval],
+
+ [
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval,
+ kF32UnboundedInterval]]
+
+
+ }
+ }
+ };
+
+ constructor() {
+ super('f32');
+ }
+
+ constants() {
+ return F32Traits._constants;
+ }
+
+ // Utilities - Overrides
+ quantize = quantizeToF32;
+ correctlyRounded = correctlyRoundedF32;
+ isFinite = isFiniteF32;
+ isSubnormal = isSubnormalNumberF32;
+ flushSubnormal = flushSubnormalNumberF32;
+ oneULP = oneULPF32;
+ scalarBuilder = f32;
+
+ // Framework - Fundamental Error Intervals - Overrides
+ absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this);
+ correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this);
+ correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this);
+ ulpInterval = this.ulpIntervalImpl.bind(this);
+
+ // Framework - API - Overrides
+ absInterval = this.absIntervalImpl.bind(this);
+ acosInterval = this.acosIntervalImpl.bind(this);
+ acoshAlternativeInterval = this.acoshAlternativeIntervalImpl.bind(this);
+ acoshPrimaryInterval = this.acoshPrimaryIntervalImpl.bind(this);
+ acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval];
+ additionInterval = this.additionIntervalImpl.bind(this);
+ additionMatrixMatrixInterval = this.additionMatrixMatrixIntervalImpl.bind(this);
+ asinInterval = this.asinIntervalImpl.bind(this);
+ asinhInterval = this.asinhIntervalImpl.bind(this);
+ atanInterval = this.atanIntervalImpl.bind(this);
+ atan2Interval = this.atan2IntervalImpl.bind(this);
+ atanhInterval = this.atanhIntervalImpl.bind(this);
+ ceilInterval = this.ceilIntervalImpl.bind(this);
+ clampMedianInterval = this.clampMedianIntervalImpl.bind(this);
+ clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this);
+ clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval];
+ cosInterval = this.cosIntervalImpl.bind(this);
+ coshInterval = this.coshIntervalImpl.bind(this);
+ crossInterval = this.crossIntervalImpl.bind(this);
+ degreesInterval = this.degreesIntervalImpl.bind(this);
+ determinantInterval = this.determinantIntervalImpl.bind(this);
+ distanceInterval = this.distanceIntervalImpl.bind(this);
+ divisionInterval = this.divisionIntervalImpl.bind(this);
+ dotInterval = this.dotIntervalImpl.bind(this);
+ expInterval = this.expIntervalImpl.bind(this);
+ exp2Interval = this.exp2IntervalImpl.bind(this);
+ faceForwardIntervals = this.faceForwardIntervalsImpl.bind(this);
+ floorInterval = this.floorIntervalImpl.bind(this);
+ fmaInterval = this.fmaIntervalImpl.bind(this);
+ fractInterval = this.fractIntervalImpl.bind(this);
+ inverseSqrtInterval = this.inverseSqrtIntervalImpl.bind(this);
+ ldexpInterval = this.ldexpIntervalImpl.bind(this);
+ lengthInterval = this.lengthIntervalImpl.bind(this);
+ logInterval = this.logIntervalImpl.bind(this);
+ log2Interval = this.log2IntervalImpl.bind(this);
+ maxInterval = this.maxIntervalImpl.bind(this);
+ minInterval = this.minIntervalImpl.bind(this);
+ mixImpreciseInterval = this.mixImpreciseIntervalImpl.bind(this);
+ mixPreciseInterval = this.mixPreciseIntervalImpl.bind(this);
+ mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval];
+ modfInterval = this.modfIntervalImpl.bind(this);
+ multiplicationInterval = this.multiplicationIntervalImpl.bind(this);
+ multiplicationMatrixMatrixInterval =
+ this.multiplicationMatrixMatrixIntervalImpl.bind(this);
+ multiplicationMatrixScalarInterval =
+ this.multiplicationMatrixScalarIntervalImpl.bind(this);
+ multiplicationScalarMatrixInterval =
+ this.multiplicationScalarMatrixIntervalImpl.bind(this);
+ multiplicationMatrixVectorInterval =
+ this.multiplicationMatrixVectorIntervalImpl.bind(this);
+ multiplicationVectorMatrixInterval =
+ this.multiplicationVectorMatrixIntervalImpl.bind(this);
+ negationInterval = this.negationIntervalImpl.bind(this);
+ normalizeInterval = this.normalizeIntervalImpl.bind(this);
+ powInterval = this.powIntervalImpl.bind(this);
+ radiansInterval = this.radiansIntervalImpl.bind(this);
+ reflectInterval = this.reflectIntervalImpl.bind(this);
+ refractInterval = this.refractIntervalImpl.bind(this);
+ remainderInterval = this.remainderIntervalImpl.bind(this);
+ roundInterval = this.roundIntervalImpl.bind(this);
+ saturateInterval = this.saturateIntervalImpl.bind(this);
+ signInterval = this.signIntervalImpl.bind(this);
+ sinInterval = this.sinIntervalImpl.bind(this);
+ sinhInterval = this.sinhIntervalImpl.bind(this);
+ smoothStepInterval = this.smoothStepIntervalImpl.bind(this);
+ sqrtInterval = this.sqrtIntervalImpl.bind(this);
+ stepInterval = this.stepIntervalImpl.bind(this);
+ subtractionInterval = this.subtractionIntervalImpl.bind(this);
+ subtractionMatrixMatrixInterval =
+ this.subtractionMatrixMatrixIntervalImpl.bind(this);
+ tanInterval = this.tanIntervalImpl.bind(this);
+ tanhInterval = this.tanhIntervalImpl.bind(this);
+ transposeInterval = this.transposeIntervalImpl.bind(this);
+ truncInterval = this.truncIntervalImpl.bind(this);
+
+ // Framework - Cases
+
+ // U32 -> Interval is used for testing f32 specific unpack* functions
+ /**
+ * @returns a Case for the param and the interval generator provided.
+ * The Case will use an interval comparator for matching results.
+ * @param param the param to pass in
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ makeU32ToVectorCase(
+ param,
+ filter,
+ ...ops)
+ {
+ param = Math.trunc(param);
+
+ const vectors = ops.map((o) => o(param));
+ if (filter === 'finite' && vectors.some((v) => !v.every((e) => e.isFinite()))) {
+ return undefined;
+ }
+ return {
+ input: u32(param),
+ expected: anyOf(...vectors)
+ };
+ }
+
+ /**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param params array of inputs to try
+ * @param filter what interval filtering to apply
+ * @param ops callbacks that implement generating an acceptance interval
+ */
+ generateU32ToIntervalCases(
+ params,
+ filter,
+ ...ops)
+ {
+ return params.reduce((cases, e) => {
+ const c = this.makeU32ToVectorCase(e, filter, ...ops);
+ if (c !== undefined) {
+ cases.push(c);
+ }
+ return cases;
+ }, new Array());
+ }
+
+ // Framework - API
+
+ QuantizeToF16IntervalOp = {
+ impl: (n) => {
+ const rounded = correctlyRoundedF16(n);
+ const flushed = addFlushedIfNeededF16(rounded);
+ return this.spanIntervals(...flushed.map((f) => this.toInterval(f)));
+ }
+ };
+
+ quantizeToF16IntervalImpl(n) {
+ return this.runScalarToIntervalOp(this.toInterval(n), this.QuantizeToF16IntervalOp);
+ }
+
+ /** Calculate an acceptance interval of quantizeToF16(x) */
+ quantizeToF16Interval = this.quantizeToF16IntervalImpl.bind(this);
+
+ /**
+ * Once-allocated ArrayBuffer/views to avoid overhead of allocation when
+ * converting between numeric formats
+ *
+ * unpackData* is shared between all the unpack*Interval functions, so to
+ * avoid re-entrancy problems, they should not call each other or themselves
+ * directly or indirectly.
+ */
+ unpackData = new ArrayBuffer(4);
+ unpackDataU32 = new Uint32Array(this.unpackData);
+ unpackDataU16 = new Uint16Array(this.unpackData);
+ unpackDataU8 = new Uint8Array(this.unpackData);
+ unpackDataI16 = new Int16Array(this.unpackData);
+ unpackDataI8 = new Int8Array(this.unpackData);
+ unpackDataF16 = new Float16Array(this.unpackData);
+
+ unpack2x16floatIntervalImpl(n) {
+ assert(
+ n >= kValue.u32.min && n <= kValue.u32.max,
+ 'unpack2x16floatInterval only accepts values on the bounds of u32'
+ );
+ this.unpackDataU32[0] = n;
+ if (this.unpackDataF16.some((f) => !isFiniteF16(f))) {
+ return [this.constants().unboundedInterval, this.constants().unboundedInterval];
+ }
+
+ const result = [
+ this.quantizeToF16Interval(this.unpackDataF16[0]),
+ this.quantizeToF16Interval(this.unpackDataF16[1])];
+
+
+ if (result.some((r) => !r.isFinite())) {
+ return [this.constants().unboundedInterval, this.constants().unboundedInterval];
+ }
+ return result;
+ }
+
+ /** Calculate an acceptance interval vector for unpack2x16float(x) */
+ unpack2x16floatInterval = this.unpack2x16floatIntervalImpl.bind(this);
+
+ unpack2x16snormIntervalImpl(n) {
+ assert(
+ n >= kValue.u32.min && n <= kValue.u32.max,
+ 'unpack2x16snormInterval only accepts values on the bounds of u32'
+ );
+ const op = (n) => {
+ return this.ulpInterval(Math.max(n / 32767, -1), 3);
+ };
+
+ this.unpackDataU32[0] = n;
+ return [op(this.unpackDataI16[0]), op(this.unpackDataI16[1])];
+ }
+
+ /** Calculate an acceptance interval vector for unpack2x16snorm(x) */
+ unpack2x16snormInterval = this.unpack2x16snormIntervalImpl.bind(this);
+
+ unpack2x16unormIntervalImpl(n) {
+ assert(
+ n >= kValue.u32.min && n <= kValue.u32.max,
+ 'unpack2x16unormInterval only accepts values on the bounds of u32'
+ );
+ const op = (n) => {
+ return this.ulpInterval(n / 65535, 3);
+ };
+
+ this.unpackDataU32[0] = n;
+ return [op(this.unpackDataU16[0]), op(this.unpackDataU16[1])];
+ }
+
+ /** Calculate an acceptance interval vector for unpack2x16unorm(x) */
+ unpack2x16unormInterval = this.unpack2x16unormIntervalImpl.bind(this);
+
+ unpack4x8snormIntervalImpl(n) {
+ assert(
+ n >= kValue.u32.min && n <= kValue.u32.max,
+ 'unpack4x8snormInterval only accepts values on the bounds of u32'
+ );
+ const op = (n) => {
+ return this.ulpInterval(Math.max(n / 127, -1), 3);
+ };
+ this.unpackDataU32[0] = n;
+ return [
+ op(this.unpackDataI8[0]),
+ op(this.unpackDataI8[1]),
+ op(this.unpackDataI8[2]),
+ op(this.unpackDataI8[3])];
+
+ }
+
+ /** Calculate an acceptance interval vector for unpack4x8snorm(x) */
+ unpack4x8snormInterval = this.unpack4x8snormIntervalImpl.bind(this);
+
+ unpack4x8unormIntervalImpl(n) {
+ assert(
+ n >= kValue.u32.min && n <= kValue.u32.max,
+ 'unpack4x8unormInterval only accepts values on the bounds of u32'
+ );
+ const op = (n) => {
+ return this.ulpInterval(n / 255, 3);
+ };
+
+ this.unpackDataU32[0] = n;
+ return [
+ op(this.unpackDataU8[0]),
+ op(this.unpackDataU8[1]),
+ op(this.unpackDataU8[2]),
+ op(this.unpackDataU8[3])];
+
+ }
+
+ /** Calculate an acceptance interval vector for unpack4x8unorm(x) */
+ unpack4x8unormInterval = this.unpack4x8unormIntervalImpl.bind(this);
+}
+
+// Need to separately allocate f32 traits, so they can be referenced by
+// FPAbstractTraits for forwarding.
+const kF32Traits = new F32Traits();
+
+// Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this
+// executes before they are defined.
+const kAbstractUnboundedInterval = new FPInterval(
+ 'abstract',
+ Number.NEGATIVE_INFINITY,
+ Number.POSITIVE_INFINITY
+);
+const kAbstractZeroInterval = new FPInterval('abstract', 0);
+
+// This is implementation is incomplete
+class FPAbstractTraits extends FPTraits {
+ static _constants = {
+ positive: {
+ min: kValue.f64.positive.min,
+ max: kValue.f64.positive.max,
+ infinity: kValue.f64.positive.infinity,
+ nearest_max: kValue.f64.positive.nearest_max,
+ less_than_one: kValue.f64.positive.less_than_one,
+ subnormal: {
+ min: kValue.f64.positive.subnormal.min,
+ max: kValue.f64.positive.subnormal.max
+ },
+ pi: {
+ whole: kValue.f64.positive.pi.whole,
+ three_quarters: kValue.f64.positive.pi.three_quarters,
+ half: kValue.f64.positive.pi.half,
+ third: kValue.f64.positive.pi.third,
+ quarter: kValue.f64.positive.pi.quarter,
+ sixth: kValue.f64.positive.pi.sixth
+ },
+ e: kValue.f64.positive.e
+ },
+ negative: {
+ min: kValue.f64.negative.min,
+ max: kValue.f64.negative.max,
+ infinity: kValue.f64.negative.infinity,
+ nearest_min: kValue.f64.negative.nearest_min,
+ less_than_one: kValue.f64.negative.less_than_one,
+ subnormal: {
+ min: kValue.f64.negative.subnormal.min,
+ max: kValue.f64.negative.subnormal.max
+ },
+ pi: {
+ whole: kValue.f64.negative.pi.whole,
+ three_quarters: kValue.f64.negative.pi.three_quarters,
+ half: kValue.f64.negative.pi.half,
+ third: kValue.f64.negative.pi.third,
+ quarter: kValue.f64.negative.pi.quarter,
+ sixth: kValue.f64.negative.pi.sixth
+ }
+ },
+ unboundedInterval: kAbstractUnboundedInterval,
+ zeroInterval: kAbstractZeroInterval,
+ // Have to use the constants.ts values here, because values defined in the
+ // initializer cannot be referenced in the initializer
+ negPiToPiInterval: new FPInterval(
+ 'abstract',
+ kValue.f64.negative.pi.whole,
+ kValue.f64.positive.pi.whole
+ ),
+ greaterThanZeroInterval: new FPInterval(
+ 'abstract',
+ kValue.f64.positive.subnormal.min,
+ kValue.f64.positive.max
+ ),
+ zeroVector: {
+ 2: [kAbstractZeroInterval, kAbstractZeroInterval],
+ 3: [kAbstractZeroInterval, kAbstractZeroInterval, kAbstractZeroInterval],
+ 4: [
+ kAbstractZeroInterval,
+ kAbstractZeroInterval,
+ kAbstractZeroInterval,
+ kAbstractZeroInterval]
+
+ },
+ unboundedVector: {
+ 2: [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ 3: [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ 4: [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval]
+
+ },
+ unboundedMatrix: {
+ 2: {
+ 2: [
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval]],
+
+ 3: [
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval]],
+
+ 4: [
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval],
+
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval]]
+
+
+ },
+ 3: {
+ 2: [
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval]],
+
+ 3: [
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval]],
+
+ 4: [
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval],
+
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval],
+
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval]]
+
+
+ },
+ 4: {
+ 2: [
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval]],
+
+ 3: [
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval],
+ [kAbstractUnboundedInterval, kAbstractUnboundedInterval, kAbstractUnboundedInterval]],
+
+ 4: [
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval],
+
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval],
+
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval],
+
+ [
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval,
+ kAbstractUnboundedInterval]]
+
+
+ }
+ }
+ };
+
+ constructor() {
+ super('abstract');
+ }
+
+ constants() {
+ return FPAbstractTraits._constants;
+ }
+
+ // Utilities - Overrides
+ // number is represented as a f64 internally, so all number values are already
+ // quantized to f64
+ quantize = (n) => {
+ return n;
+ };
+ correctlyRounded = correctlyRoundedF64;
+ isFinite = Number.isFinite;
+ isSubnormal = isSubnormalNumberF64;
+ flushSubnormal = flushSubnormalNumberF64;
+ oneULP = (_target, _mode = 'flush') => {
+ unreachable(`'FPAbstractTraits.oneULP should never be called`);
+ };
+ scalarBuilder = abstractFloat;
+
+ // Framework - Fundamental Error Intervals - Overrides
+ absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this);
+ correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this);
+ correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this);
+ ulpInterval = (n, numULP) => {
+ return this.toInterval(kF32Traits.ulpInterval(n, numULP));
+ };
+
+ // Framework - API - Overrides
+ absInterval = this.absIntervalImpl.bind(this);
+ acosInterval = this.unimplementedScalarToInterval.bind(this, 'acosInterval');
+ acoshAlternativeInterval = this.unimplementedScalarToInterval.bind(
+ this,
+ 'acoshAlternativeInterval'
+ );
+ acoshPrimaryInterval = this.unimplementedScalarToInterval.bind(
+ this,
+ 'acoshPrimaryInterval'
+ );
+ acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval];
+ additionInterval = this.additionIntervalImpl.bind(this);
+ additionMatrixMatrixInterval = this.additionMatrixMatrixIntervalImpl.bind(this);
+ asinInterval = this.unimplementedScalarToInterval.bind(this, 'asinInterval');
+ asinhInterval = this.unimplementedScalarToInterval.bind(this, 'asinhInterval');
+ atanInterval = this.unimplementedScalarToInterval.bind(this, 'atanInterval');
+ atan2Interval = this.unimplementedScalarPairToInterval.bind(
+ this,
+ 'atan2Interval'
+ );
+ atanhInterval = this.unimplementedScalarToInterval.bind(this, 'atanhInterval');
+ ceilInterval = this.unimplementedScalarToInterval.bind(this, 'ceilInterval');
+ clampMedianInterval = this.clampMedianIntervalImpl.bind(this);
+ clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this);
+ clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval];
+ cosInterval = this.unimplementedScalarToInterval.bind(this, 'cosInterval');
+ coshInterval = this.unimplementedScalarToInterval.bind(this, 'coshInterval');
+ crossInterval = this.crossIntervalImpl.bind(this);
+ degreesInterval = this.degreesIntervalImpl.bind(this);
+ determinantInterval = this.unimplementedMatrixToInterval.bind(
+ this,
+ 'determinantInterval'
+ );
+ distanceInterval = this.unimplementedDistance.bind(this);
+ divisionInterval = (
+ x,
+ y) =>
+ {
+ return this.toInterval(kF32Traits.divisionInterval(x, y));
+ };
+ dotInterval = this.unimplementedVectorPairToInterval.bind(this, 'dotInterval');
+ expInterval = this.unimplementedScalarToInterval.bind(this, 'expInterval');
+ exp2Interval = this.unimplementedScalarToInterval.bind(this, 'exp2Interval');
+ faceForwardIntervals = this.unimplementedFaceForward.bind(this);
+ floorInterval = this.floorIntervalImpl.bind(this);
+ fmaInterval = this.fmaIntervalImpl.bind(this);
+ fractInterval = this.unimplementedScalarToInterval.bind(this, 'fractInterval');
+ inverseSqrtInterval = this.unimplementedScalarToInterval.bind(
+ this,
+ 'inverseSqrtInterval'
+ );
+ ldexpInterval = this.unimplementedScalarPairToInterval.bind(
+ this,
+ 'ldexpInterval'
+ );
+ lengthInterval = this.unimplementedLength.bind(this);
+ logInterval = this.unimplementedScalarToInterval.bind(this, 'logInterval');
+ log2Interval = this.unimplementedScalarToInterval.bind(this, 'log2Interval');
+ maxInterval = this.maxIntervalImpl.bind(this);
+ minInterval = this.minIntervalImpl.bind(this);
+ mixImpreciseInterval = this.unimplementedScalarTripleToInterval.bind(
+ this,
+ 'mixImpreciseInterval'
+ );
+ mixPreciseInterval = this.unimplementedScalarTripleToInterval.bind(
+ this,
+ 'mixPreciseInterval'
+ );
+ mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval];
+ modfInterval = this.modfIntervalImpl.bind(this);
+ multiplicationInterval = this.multiplicationIntervalImpl.bind(this);
+ multiplicationMatrixMatrixInterval = this.unimplementedMatrixPairToMatrix.bind(
+ this,
+ 'multiplicationMatrixMatrixInterval'
+ );
+ multiplicationMatrixScalarInterval = this.unimplementedMatrixScalarToMatrix.bind(
+ this,
+ 'multiplicationMatrixScalarInterval'
+ );
+ multiplicationScalarMatrixInterval = this.unimplementedScalarMatrixToMatrix.bind(
+ this,
+ 'multiplicationScalarMatrixInterval'
+ );
+ multiplicationMatrixVectorInterval = this.unimplementedMatrixVectorToVector.bind(
+ this,
+ 'multiplicationMatrixVectorInterval'
+ );
+ multiplicationVectorMatrixInterval = this.unimplementedVectorMatrixToVector.bind(
+ this,
+ 'multiplicationVectorMatrixInterval'
+ );
+ negationInterval = this.negationIntervalImpl.bind(this);
+ normalizeInterval = this.unimplementedVectorToVector.bind(
+ this,
+ 'normalizeInterval'
+ );
+ powInterval = this.unimplementedScalarPairToInterval.bind(this, 'powInterval');
+ radiansInterval = this.radiansIntervalImpl.bind(this);
+ reflectInterval = this.unimplementedVectorPairToVector.bind(
+ this,
+ 'reflectInterval'
+ );
+ refractInterval = this.unimplementedRefract.bind(this);
+ remainderInterval = (x, y) => {
+ return this.toInterval(kF32Traits.remainderInterval(x, y));
+ };
+ roundInterval = this.unimplementedScalarToInterval.bind(this, 'roundInterval');
+ saturateInterval = this.saturateIntervalImpl.bind(this);
+ signInterval = this.signIntervalImpl.bind(this);
+ sinInterval = this.unimplementedScalarToInterval.bind(this, 'sinInterval');
+ sinhInterval = this.unimplementedScalarToInterval.bind(this, 'sinhInterval');
+ smoothStepInterval = this.unimplementedScalarTripleToInterval.bind(
+ this,
+ 'smoothStepInterval'
+ );
+ sqrtInterval = this.unimplementedScalarToInterval.bind(this, 'sqrtInterval');
+ stepInterval = this.unimplementedScalarPairToInterval.bind(this, 'stepInterval');
+ subtractionInterval = this.subtractionIntervalImpl.bind(this);
+ subtractionMatrixMatrixInterval =
+ this.subtractionMatrixMatrixIntervalImpl.bind(this);
+ tanInterval = this.unimplementedScalarToInterval.bind(this, 'tanInterval');
+ tanhInterval = this.unimplementedScalarToInterval.bind(this, 'tanhInterval');
+ transposeInterval = this.transposeIntervalImpl.bind(this);
+ truncInterval = this.truncIntervalImpl.bind(this);
+}
+
+// Pre-defined values that get used multiple times in _constants' initializers. Cannot use FPTraits members, since this
+// executes before they are defined.
+const kF16UnboundedInterval = new FPInterval(
+ 'f16',
+ Number.NEGATIVE_INFINITY,
+ Number.POSITIVE_INFINITY
+);
+const kF16ZeroInterval = new FPInterval('f16', 0);
+
+// This is implementation is incomplete
+class F16Traits extends FPTraits {
+ static _constants = {
+ positive: {
+ min: kValue.f16.positive.min,
+ max: kValue.f16.positive.max,
+ infinity: kValue.f16.positive.infinity,
+ nearest_max: kValue.f16.positive.nearest_max,
+ less_than_one: kValue.f16.positive.less_than_one,
+ subnormal: {
+ min: kValue.f16.positive.subnormal.min,
+ max: kValue.f16.positive.subnormal.max
+ },
+ pi: {
+ whole: kValue.f16.positive.pi.whole,
+ three_quarters: kValue.f16.positive.pi.three_quarters,
+ half: kValue.f16.positive.pi.half,
+ third: kValue.f16.positive.pi.third,
+ quarter: kValue.f16.positive.pi.quarter,
+ sixth: kValue.f16.positive.pi.sixth
+ },
+ e: kValue.f16.positive.e
+ },
+ negative: {
+ min: kValue.f16.negative.min,
+ max: kValue.f16.negative.max,
+ infinity: kValue.f16.negative.infinity,
+ nearest_min: kValue.f16.negative.nearest_min,
+ less_than_one: kValue.f16.negative.less_than_one,
+ subnormal: {
+ min: kValue.f16.negative.subnormal.min,
+ max: kValue.f16.negative.subnormal.max
+ },
+ pi: {
+ whole: kValue.f16.negative.pi.whole,
+ three_quarters: kValue.f16.negative.pi.three_quarters,
+ half: kValue.f16.negative.pi.half,
+ third: kValue.f16.negative.pi.third,
+ quarter: kValue.f16.negative.pi.quarter,
+ sixth: kValue.f16.negative.pi.sixth
+ }
+ },
+ unboundedInterval: kF16UnboundedInterval,
+ zeroInterval: kF16ZeroInterval,
+ // Have to use the constants.ts values here, because values defined in the
+ // initializer cannot be referenced in the initializer
+ negPiToPiInterval: new FPInterval(
+ 'f16',
+ kValue.f16.negative.pi.whole,
+ kValue.f16.positive.pi.whole
+ ),
+ greaterThanZeroInterval: new FPInterval(
+ 'f16',
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.max
+ ),
+ zeroVector: {
+ 2: [kF16ZeroInterval, kF16ZeroInterval],
+ 3: [kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval],
+ 4: [kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval]
+ },
+ unboundedVector: {
+ 2: [kF16UnboundedInterval, kF16UnboundedInterval],
+ 3: [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ 4: [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval]
+
+ },
+ unboundedMatrix: {
+ 2: {
+ 2: [
+ [kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval]],
+
+ 3: [
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval]],
+
+ 4: [
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval],
+
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval]]
+
+
+ },
+ 3: {
+ 2: [
+ [kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval]],
+
+ 3: [
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval]],
+
+ 4: [
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval],
+
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval],
+
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval]]
+
+
+ },
+ 4: {
+ 2: [
+ [kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval]],
+
+ 3: [
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval],
+ [kF16UnboundedInterval, kF16UnboundedInterval, kF16UnboundedInterval]],
+
+ 4: [
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval],
+
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval],
+
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval],
+
+ [
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval,
+ kF16UnboundedInterval]]
+
+
+ }
+ }
+ };
+
+ constructor() {
+ super('f16');
+ }
+
+ constants() {
+ return F16Traits._constants;
+ }
+
+ // Utilities - Overrides
+ quantize = quantizeToF16;
+ correctlyRounded = correctlyRoundedF16;
+ isFinite = isFiniteF16;
+ isSubnormal = isSubnormalNumberF16;
+ flushSubnormal = flushSubnormalNumberF16;
+ oneULP = oneULPF16;
+ scalarBuilder = f16;
+
+ // Framework - Fundamental Error Intervals - Overrides
+ absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this);
+ correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this);
+ correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this);
+ ulpInterval = this.ulpIntervalImpl.bind(this);
+
+ // Framework - API - Overrides
+ absInterval = this.absIntervalImpl.bind(this);
+ acosInterval = this.acosIntervalImpl.bind(this);
+ acoshAlternativeInterval = this.acoshAlternativeIntervalImpl.bind(this);
+ acoshPrimaryInterval = this.acoshPrimaryIntervalImpl.bind(this);
+ acoshIntervals = [this.acoshAlternativeInterval, this.acoshPrimaryInterval];
+ additionInterval = this.additionIntervalImpl.bind(this);
+ additionMatrixMatrixInterval = this.additionMatrixMatrixIntervalImpl.bind(this);
+ asinInterval = this.asinIntervalImpl.bind(this);
+ asinhInterval = this.asinhIntervalImpl.bind(this);
+ atanInterval = this.atanIntervalImpl.bind(this);
+ atan2Interval = this.atan2IntervalImpl.bind(this);
+ atanhInterval = this.atanhIntervalImpl.bind(this);
+ ceilInterval = this.ceilIntervalImpl.bind(this);
+ clampMedianInterval = this.clampMedianIntervalImpl.bind(this);
+ clampMinMaxInterval = this.clampMinMaxIntervalImpl.bind(this);
+ clampIntervals = [this.clampMedianInterval, this.clampMinMaxInterval];
+ cosInterval = this.cosIntervalImpl.bind(this);
+ coshInterval = this.coshIntervalImpl.bind(this);
+ crossInterval = this.crossIntervalImpl.bind(this);
+ degreesInterval = this.degreesIntervalImpl.bind(this);
+ determinantInterval = this.determinantIntervalImpl.bind(this);
+ distanceInterval = this.distanceIntervalImpl.bind(this);
+ divisionInterval = this.divisionIntervalImpl.bind(this);
+ dotInterval = this.dotIntervalImpl.bind(this);
+ expInterval = this.expIntervalImpl.bind(this);
+ exp2Interval = this.exp2IntervalImpl.bind(this);
+ faceForwardIntervals = this.faceForwardIntervalsImpl.bind(this);
+ floorInterval = this.floorIntervalImpl.bind(this);
+ fmaInterval = this.fmaIntervalImpl.bind(this);
+ fractInterval = this.fractIntervalImpl.bind(this);
+ inverseSqrtInterval = this.inverseSqrtIntervalImpl.bind(this);
+ ldexpInterval = this.ldexpIntervalImpl.bind(this);
+ lengthInterval = this.lengthIntervalImpl.bind(this);
+ logInterval = this.logIntervalImpl.bind(this);
+ log2Interval = this.log2IntervalImpl.bind(this);
+ maxInterval = this.maxIntervalImpl.bind(this);
+ minInterval = this.minIntervalImpl.bind(this);
+ mixImpreciseInterval = this.mixImpreciseIntervalImpl.bind(this);
+ mixPreciseInterval = this.mixPreciseIntervalImpl.bind(this);
+ mixIntervals = [this.mixImpreciseInterval, this.mixPreciseInterval];
+ modfInterval = this.modfIntervalImpl.bind(this);
+ multiplicationInterval = this.multiplicationIntervalImpl.bind(this);
+ multiplicationMatrixMatrixInterval =
+ this.multiplicationMatrixMatrixIntervalImpl.bind(this);
+ multiplicationMatrixScalarInterval =
+ this.multiplicationMatrixScalarIntervalImpl.bind(this);
+ multiplicationScalarMatrixInterval =
+ this.multiplicationScalarMatrixIntervalImpl.bind(this);
+ multiplicationMatrixVectorInterval =
+ this.multiplicationMatrixVectorIntervalImpl.bind(this);
+ multiplicationVectorMatrixInterval =
+ this.multiplicationVectorMatrixIntervalImpl.bind(this);
+ negationInterval = this.negationIntervalImpl.bind(this);
+ normalizeInterval = this.normalizeIntervalImpl.bind(this);
+ powInterval = this.powIntervalImpl.bind(this);
+ radiansInterval = this.radiansIntervalImpl.bind(this);
+ reflectInterval = this.reflectIntervalImpl.bind(this);
+ refractInterval = this.refractIntervalImpl.bind(this);
+ remainderInterval = this.remainderIntervalImpl.bind(this);
+ roundInterval = this.roundIntervalImpl.bind(this);
+ saturateInterval = this.saturateIntervalImpl.bind(this);
+ signInterval = this.signIntervalImpl.bind(this);
+ sinInterval = this.sinIntervalImpl.bind(this);
+ sinhInterval = this.sinhIntervalImpl.bind(this);
+ smoothStepInterval = this.smoothStepIntervalImpl.bind(this);
+ sqrtInterval = this.sqrtIntervalImpl.bind(this);
+ stepInterval = this.stepIntervalImpl.bind(this);
+ subtractionInterval = this.subtractionIntervalImpl.bind(this);
+ subtractionMatrixMatrixInterval =
+ this.subtractionMatrixMatrixIntervalImpl.bind(this);
+ tanInterval = this.tanIntervalImpl.bind(this);
+ tanhInterval = this.tanhIntervalImpl.bind(this);
+ transposeInterval = this.transposeIntervalImpl.bind(this);
+ truncInterval = this.truncIntervalImpl.bind(this);
+}
+
+export const FP = {
+ f32: kF32Traits,
+ f16: new F16Traits(),
+ abstract: new FPAbstractTraits()
+};
+
+/** @returns the floating-point traits for `type` */
+export function fpTraitsFor(type) {
+ switch (type.kind) {
+ case 'abstract-float':
+ return FP.abstract;
+ case 'f32':
+ return FP.f32;
+ case 'f16':
+ return FP.f16;
+ default:
+ unreachable(`unsupported type: ${type}`);
+ }
+}
+
+/** @returns true if the value `value` is representable with `type` */
+export function isRepresentable(value, type) {
+ if (!Number.isFinite(value)) {
+ return false;
+ }
+ if (isFloatType(type)) {
+ const constants = fpTraitsFor(type).constants();
+ return value >= constants.negative.min && value <= constants.positive.max;
+ }
+ assert(false, `isRepresentable() is not yet implemented for type ${type}`);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/math.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/math.js
new file mode 100644
index 0000000000..873c4315ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/math.js
@@ -0,0 +1,2247 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../common/util/util.js';import {
+ Float16Array,
+ getFloat16,
+ hfround,
+ setFloat16 } from
+'../../external/petamoriken/float16/float16.js';
+
+import { kBit, kValue } from './constants.js';
+import {
+ reinterpretF64AsU64,
+ reinterpretU64AsF64,
+ reinterpretU32AsF32,
+ reinterpretU16AsF16 } from
+'./reinterpret.js';
+
+/**
+ * A multiple of 8 guaranteed to be way too large to allocate (just under 8 pebibytes).
+ * This is a "safe" integer (ULP <= 1.0) very close to MAX_SAFE_INTEGER.
+ *
+ * Note: allocations of this size are likely to exceed limitations other than just the system's
+ * physical memory, so test cases are also needed to try to trigger "true" OOM.
+ */
+export const kMaxSafeMultipleOf8 = Number.MAX_SAFE_INTEGER - 7;
+
+/** Round `n` up to the next multiple of `alignment` (inclusive). */
+// MAINTENANCE_TODO: Rename to `roundUp`
+export function align(n, alignment) {
+ assert(Number.isInteger(n) && n >= 0, 'n must be a non-negative integer');
+ assert(Number.isInteger(alignment) && alignment > 0, 'alignment must be a positive integer');
+ return Math.ceil(n / alignment) * alignment;
+}
+
+/** Round `n` down to the next multiple of `alignment` (inclusive). */
+export function roundDown(n, alignment) {
+ assert(Number.isInteger(n) && n >= 0, 'n must be a non-negative integer');
+ assert(Number.isInteger(alignment) && alignment > 0, 'alignment must be a positive integer');
+ return Math.floor(n / alignment) * alignment;
+}
+
+/** Clamp a number to the provided range. */
+export function clamp(n, { min, max }) {
+ assert(max >= min);
+ return Math.min(Math.max(n, min), max);
+}
+
+/** @returns 0 if |val| is a subnormal f64 number, otherwise returns |val| */
+export function flushSubnormalNumberF64(val) {
+ return isSubnormalNumberF64(val) ? 0 : val;
+}
+
+/** @returns if number is within subnormal range of f64 */
+export function isSubnormalNumberF64(n) {
+ return n > kValue.f64.negative.max && n < kValue.f64.positive.min;
+}
+
+/** @returns 0 if |val| is a subnormal f32 number, otherwise returns |val| */
+export function flushSubnormalNumberF32(val) {
+ return isSubnormalNumberF32(val) ? 0 : val;
+}
+
+/** @returns if number is within subnormal range of f32 */
+export function isSubnormalNumberF32(n) {
+ return n > kValue.f32.negative.max && n < kValue.f32.positive.min;
+}
+
+/** @returns if number is in the finite range of f32 */
+export function isFiniteF32(n) {
+ return n >= kValue.f32.negative.min && n <= kValue.f32.positive.max;
+}
+
+/** @returns 0 if |val| is a subnormal f16 number, otherwise returns |val| */
+export function flushSubnormalNumberF16(val) {
+ return isSubnormalNumberF16(val) ? 0 : val;
+}
+
+/** @returns if number is within subnormal range of f16 */
+export function isSubnormalNumberF16(n) {
+ return n > kValue.f16.negative.max && n < kValue.f16.positive.min;
+}
+
+/** @returns if number is in the finite range of f16 */
+export function isFiniteF16(n) {
+ return n >= kValue.f16.negative.min && n <= kValue.f16.positive.max;
+}
+
+/** Should FTZ occur during calculations or not */
+
+
+/** Should nextAfter calculate towards positive infinity or negative infinity */
+
+
+/**
+ * Once-allocated ArrayBuffer/views to avoid overhead of allocation when
+ * converting between numeric formats
+ *
+ * Usage of a once-allocated pattern like this makes nextAfterF64 non-reentrant,
+ * so cannot call itself directly or indirectly.
+ */
+const nextAfterF64Data = new ArrayBuffer(8);
+const nextAfterF64Int = new BigUint64Array(nextAfterF64Data);
+const nextAfterF64Float = new Float64Array(nextAfterF64Data);
+
+/**
+ * @returns the next f64 value after |val|, towards +inf or -inf as specified by |dir|.
+
+ * If |mode| is 'flush', all subnormal values will be flushed to 0,
+ * before processing and for -/+0 the nextAfterF64 will be the closest normal in
+ * the correct direction.
+
+ * If |mode| is 'no-flush', the next subnormal will be calculated when appropriate,
+ * and for -/+0 the nextAfterF64 will be the closest subnormal in the correct
+ * direction.
+ *
+ * val needs to be in [min f64, max f64]
+ */
+export function nextAfterF64(val, dir, mode) {
+ if (Number.isNaN(val)) {
+ return val;
+ }
+
+ if (val === Number.POSITIVE_INFINITY) {
+ return kValue.f64.positive.infinity;
+ }
+
+ if (val === Number.NEGATIVE_INFINITY) {
+ return kValue.f64.negative.infinity;
+ }
+
+ assert(
+ val <= kValue.f64.positive.max && val >= kValue.f64.negative.min,
+ `${val} is not in the range of f64`
+ );
+
+ val = mode === 'flush' ? flushSubnormalNumberF64(val) : val;
+
+ // -/+0 === 0 returns true
+ if (val === 0) {
+ if (dir === 'positive') {
+ return mode === 'flush' ? kValue.f64.positive.min : kValue.f64.positive.subnormal.min;
+ } else {
+ return mode === 'flush' ? kValue.f64.negative.max : kValue.f64.negative.subnormal.max;
+ }
+ }
+
+ nextAfterF64Float[0] = val;
+ const is_positive = (nextAfterF64Int[0] & 0x8000_0000_0000_0000n) === 0n;
+ if (is_positive === (dir === 'positive')) {
+ nextAfterF64Int[0] += 1n;
+ } else {
+ nextAfterF64Int[0] -= 1n;
+ }
+
+ // Checking for overflow
+ if ((nextAfterF64Int[0] & 0x7ff0_0000_0000_0000n) === 0x7ff0_0000_0000_0000n) {
+ if (dir === 'positive') {
+ return kValue.f64.positive.infinity;
+ } else {
+ return kValue.f64.negative.infinity;
+ }
+ }
+
+ return mode === 'flush' ? flushSubnormalNumberF64(nextAfterF64Float[0]) : nextAfterF64Float[0];
+}
+
+/**
+ * Once-allocated ArrayBuffer/views to avoid overhead of allocation when
+ * converting between numeric formats
+ *
+ * Usage of a once-allocated pattern like this makes nextAfterF32 non-reentrant,
+ * so cannot call itself directly or indirectly.
+ */
+const nextAfterF32Data = new ArrayBuffer(4);
+const nextAfterF32Int = new Uint32Array(nextAfterF32Data);
+const nextAfterF32Float = new Float32Array(nextAfterF32Data);
+
+/**
+ * @returns the next f32 value after |val|, towards +inf or -inf as specified by |dir|.
+
+ * If |mode| is 'flush', all subnormal values will be flushed to 0,
+ * before processing and for -/+0 the nextAfterF32 will be the closest normal in
+ * the correct direction.
+
+ * If |mode| is 'no-flush', the next subnormal will be calculated when appropriate,
+ * and for -/+0 the nextAfterF32 will be the closest subnormal in the correct
+ * direction.
+ *
+ * val needs to be in [min f32, max f32]
+ */
+export function nextAfterF32(val, dir, mode) {
+ if (Number.isNaN(val)) {
+ return val;
+ }
+
+ if (val === Number.POSITIVE_INFINITY) {
+ return kValue.f32.positive.infinity;
+ }
+
+ if (val === Number.NEGATIVE_INFINITY) {
+ return kValue.f32.negative.infinity;
+ }
+
+ assert(
+ val <= kValue.f32.positive.max && val >= kValue.f32.negative.min,
+ `${val} is not in the range of f32`
+ );
+
+ val = mode === 'flush' ? flushSubnormalNumberF32(val) : val;
+
+ // -/+0 === 0 returns true
+ if (val === 0) {
+ if (dir === 'positive') {
+ return mode === 'flush' ? kValue.f32.positive.min : kValue.f32.positive.subnormal.min;
+ } else {
+ return mode === 'flush' ? kValue.f32.negative.max : kValue.f32.negative.subnormal.max;
+ }
+ }
+
+ nextAfterF32Float[0] = val; // This quantizes from number (f64) to f32
+ if (
+ dir === 'positive' && nextAfterF32Float[0] <= val ||
+ dir === 'negative' && nextAfterF32Float[0] >= val)
+ {
+ // val is either f32 precise or quantizing rounded in the opposite direction
+ // from what is needed, so need to calculate the value in the correct
+ // direction.
+ const is_positive = (nextAfterF32Int[0] & 0x80000000) === 0;
+ if (is_positive === (dir === 'positive')) {
+ nextAfterF32Int[0] += 1;
+ } else {
+ nextAfterF32Int[0] -= 1;
+ }
+ }
+
+ // Checking for overflow
+ if ((nextAfterF32Int[0] & 0x7f800000) === 0x7f800000) {
+ if (dir === 'positive') {
+ return kValue.f32.positive.infinity;
+ } else {
+ return kValue.f32.negative.infinity;
+ }
+ }
+
+ return mode === 'flush' ? flushSubnormalNumberF32(nextAfterF32Float[0]) : nextAfterF32Float[0];
+}
+
+/**
+ * Once-allocated ArrayBuffer/views to avoid overhead of allocation when
+ * converting between numeric formats
+ *
+ * Usage of a once-allocated pattern like this makes nextAfterF16 non-reentrant,
+ * so cannot call itself directly or indirectly.
+ */
+const nextAfterF16Data = new ArrayBuffer(2);
+const nextAfterF16Hex = new Uint16Array(nextAfterF16Data);
+const nextAfterF16Float = new Float16Array(nextAfterF16Data);
+
+/**
+ * @returns the next f16 value after |val|, towards +inf or -inf as specified by |dir|.
+
+ * If |mode| is 'flush', all subnormal values will be flushed to 0,
+ * before processing and for -/+0 the nextAfterF16 will be the closest normal in
+ * the correct direction.
+
+ * If |mode| is 'no-flush', the next subnormal will be calculated when appropriate,
+ * and for -/+0 the nextAfterF16 will be the closest subnormal in the correct
+ * direction.
+ *
+ * val needs to be in [min f16, max f16]
+ */
+export function nextAfterF16(val, dir, mode) {
+ if (Number.isNaN(val)) {
+ return val;
+ }
+
+ if (val === Number.POSITIVE_INFINITY) {
+ return kValue.f16.positive.infinity;
+ }
+
+ if (val === Number.NEGATIVE_INFINITY) {
+ return kValue.f16.negative.infinity;
+ }
+
+ assert(
+ val <= kValue.f16.positive.max && val >= kValue.f16.negative.min,
+ `${val} is not in the range of f16`
+ );
+
+ val = mode === 'flush' ? flushSubnormalNumberF16(val) : val;
+
+ // -/+0 === 0 returns true
+ if (val === 0) {
+ if (dir === 'positive') {
+ return mode === 'flush' ? kValue.f16.positive.min : kValue.f16.positive.subnormal.min;
+ } else {
+ return mode === 'flush' ? kValue.f16.negative.max : kValue.f16.negative.subnormal.max;
+ }
+ }
+
+ nextAfterF16Float[0] = val; // This quantizes from number (f64) to f16
+ if (
+ dir === 'positive' && nextAfterF16Float[0] <= val ||
+ dir === 'negative' && nextAfterF16Float[0] >= val)
+ {
+ // val is either f16 precise or quantizing rounded in the opposite direction
+ // from what is needed, so need to calculate the value in the correct
+ // direction.
+ const is_positive = (nextAfterF16Hex[0] & 0x8000) === 0;
+ if (is_positive === (dir === 'positive')) {
+ nextAfterF16Hex[0] += 1;
+ } else {
+ nextAfterF16Hex[0] -= 1;
+ }
+ }
+
+ // Checking for overflow
+ if ((nextAfterF16Hex[0] & 0x7c00) === 0x7c00) {
+ if (dir === 'positive') {
+ return kValue.f16.positive.infinity;
+ } else {
+ return kValue.f16.negative.infinity;
+ }
+ }
+
+ return mode === 'flush' ? flushSubnormalNumberF16(nextAfterF16Float[0]) : nextAfterF16Float[0];
+}
+
+/**
+ * @returns ulp(x), the unit of least precision for a specific number as a 64-bit float
+ *
+ * ulp(x) is the distance between the two floating point numbers nearest x.
+ * This value is also called unit of last place, ULP, and 1 ULP.
+ * See the WGSL spec and http://www.ens-lyon.fr/LIP/Pub/Rapports/RR/RR2005/RR2005-09.pdf
+ * for a more detailed/nuanced discussion of the definition of ulp(x).
+ *
+ * @param target number to calculate ULP for
+ * @param mode should FTZ occurring during calculation or not
+ */
+export function oneULPF64(target, mode = 'flush') {
+ if (Number.isNaN(target)) {
+ return Number.NaN;
+ }
+
+ target = mode === 'flush' ? flushSubnormalNumberF64(target) : target;
+
+ // For values out of bounds for f64 ulp(x) is defined as the
+ // distance between the two nearest f64 representable numbers to the
+ // appropriate edge, which also happens to be the maximum possible ULP.
+ if (
+ target === Number.POSITIVE_INFINITY ||
+ target >= kValue.f64.positive.max ||
+ target === Number.NEGATIVE_INFINITY ||
+ target <= kValue.f64.negative.min)
+ {
+ return kValue.f64.max_ulp;
+ }
+
+ // ulp(x) is min(after - before), where
+ // before <= x <= after
+ // before =/= after
+ // before and after are f64 representable
+ const before = nextAfterF64(target, 'negative', mode);
+ const after = nextAfterF64(target, 'positive', mode);
+ // Since number is internally a f64, |target| is always f64 representable, so
+ // either before or after will be x
+ return Math.min(target - before, after - target);
+}
+
+/**
+ * @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.
+ * This value is also called unit of last place, ULP, and 1 ULP.
+ * See the WGSL spec and http://www.ens-lyon.fr/LIP/Pub/Rapports/RR/RR2005/RR2005-09.pdf
+ * for a more detailed/nuanced discussion of the definition of ulp(x).
+ *
+ * @param target number to calculate ULP for
+ * @param mode should FTZ occurring during calculation or not
+ */
+export function oneULPF32(target, mode = 'flush') {
+ if (Number.isNaN(target)) {
+ return Number.NaN;
+ }
+
+ target = mode === 'flush' ? flushSubnormalNumberF32(target) : target;
+
+ // For values out of bounds for f32 ulp(x) is defined as the
+ // distance between the two nearest f32 representable numbers to the
+ // appropriate edge, which also happens to be the maximum possible ULP.
+ if (
+ target === Number.POSITIVE_INFINITY ||
+ target >= kValue.f32.positive.max ||
+ target === Number.NEGATIVE_INFINITY ||
+ target <= kValue.f32.negative.min)
+ {
+ return kValue.f32.max_ulp;
+ }
+
+ // ulp(x) is min(after - before), where
+ // before <= x <= after
+ // before =/= after
+ // before and after are f32 representable
+ const before = nextAfterF32(target, 'negative', mode);
+ const after = nextAfterF32(target, 'positive', mode);
+ const converted = quantizeToF32(target);
+ if (converted === target) {
+ // |target| is f32 representable, so either before or after will be x
+ return Math.min(target - before, after - target);
+ } else {
+ // |target| is not f32 representable so taking distance of neighbouring f32s.
+ return after - before;
+ }
+}
+
+/**
+ * @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.
+ * This value is also called unit of last place, ULP, and 1 ULP.
+ * See the WGSL spec and http://www.ens-lyon.fr/LIP/Pub/Rapports/RR/RR2005/RR2005-09.pdf
+ * for a more detailed/nuanced discussion of the definition of ulp(x).
+ *
+ * @param target number to calculate ULP for
+ * @param mode should FTZ occurring during calculation or not
+ */
+export function oneULPF16(target, mode = 'flush') {
+ if (Number.isNaN(target)) {
+ return Number.NaN;
+ }
+
+ target = mode === 'flush' ? flushSubnormalNumberF16(target) : target;
+
+ // For values out of bounds for f16 ulp(x) is defined as the
+ // distance between the two nearest f16 representable numbers to the
+ // appropriate edge, which also happens to be the maximum possible ULP.
+ if (
+ target === Number.POSITIVE_INFINITY ||
+ target >= kValue.f16.positive.max ||
+ target === Number.NEGATIVE_INFINITY ||
+ target <= kValue.f16.negative.min)
+ {
+ return kValue.f16.max_ulp;
+ }
+
+ // ulp(x) is min(after - before), where
+ // before <= x <= after
+ // before =/= after
+ // before and after are f16 representable
+ const before = nextAfterF16(target, 'negative', mode);
+ const after = nextAfterF16(target, 'positive', mode);
+ const converted = quantizeToF16(target);
+ if (converted === target) {
+ // |target| is f16 representable, so either before or after will be x
+ return Math.min(target - before, after - target);
+ } else {
+ // |target| is not f16 representable so taking distance of neighbouring f16s.
+ return after - before;
+ }
+}
+
+/**
+ * Calculate the valid roundings when quantizing to 64-bit floats
+ *
+ * TS/JS's number type is internally a f64, so the supplied value will be
+ * quanitized by definition. The only corner cases occur if a non-finite value
+ * is provided, since the valid roundings include the appropriate min or max
+ * value.
+ *
+ * @param n number to be quantized
+ * @returns all of the acceptable roundings for quantizing to 64-bits in
+ * ascending order.
+ */
+export function correctlyRoundedF64(n) {
+ assert(!Number.isNaN(n), `correctlyRoundedF32 not defined for NaN`);
+ // Above f64 range
+ if (n === Number.POSITIVE_INFINITY) {
+ return [kValue.f64.positive.max, Number.POSITIVE_INFINITY];
+ }
+
+ // Below f64 range
+ if (n === Number.NEGATIVE_INFINITY) {
+ return [Number.NEGATIVE_INFINITY, kValue.f64.negative.min];
+ }
+
+ return [n];
+}
+
+/**
+ * Calculate the valid roundings when quantizing to 32-bit floats
+ *
+ * TS/JS's number type is internally a f64, so quantization needs to occur when
+ * converting to f32 for WGSL. WGSL does not specify a specific rounding mode,
+ * so if a number is not precisely representable in 32-bits, but in the
+ * range, there are two possible valid quantizations. If it is precisely
+ * representable, there is only one valid quantization. This function calculates
+ * the valid roundings and returns them in an array.
+ *
+ * This function does not consider flushing mode, so subnormals are maintained.
+ * The caller is responsible to flushing before and after as appropriate.
+ *
+ * Out of bounds values need to consider how they interact with the overflow
+ * rules.
+ * * If a value is OOB but not too far out, an implementation may choose to round
+ * to nearest finite value or the correct infinity. This boundary is at
+ * 2^(f32.emax + 1) and -(2^(f32.emax + 1)) respectively.
+ * Values that are at or beyond these limits must be rounded towards the
+ * appropriate infinity.
+ *
+ * @param n number to be quantized
+ * @returns all of the acceptable roundings for quantizing to 32-bits in
+ * ascending order.
+ */
+export function correctlyRoundedF32(n) {
+ if (Number.isNaN(n)) {
+ return [n];
+ }
+
+ // Greater than or equal to the upper overflow boundry
+ if (n >= 2 ** (kValue.f32.emax + 1)) {
+ return [Number.POSITIVE_INFINITY];
+ }
+
+ // OOB, but less than the upper overflow boundary
+ if (n > kValue.f32.positive.max) {
+ return [kValue.f32.positive.max, Number.POSITIVE_INFINITY];
+ }
+
+ // f32 finite
+ if (n <= kValue.f32.positive.max && n >= kValue.f32.negative.min) {
+ const n_32 = quantizeToF32(n);
+ if (n === n_32) {
+ // n is precisely expressible as a f32, so should not be rounded
+ return [n];
+ }
+
+ if (n_32 > n) {
+ // n_32 rounded towards +inf, so is after n
+ const other = nextAfterF32(n_32, 'negative', 'no-flush');
+ return [other, n_32];
+ } else {
+ // n_32 rounded towards -inf, so is before n
+ const other = nextAfterF32(n_32, 'positive', 'no-flush');
+ return [n_32, other];
+ }
+ }
+
+ // OOB, but greater the lower overflow boundary
+ if (n > -(2 ** (kValue.f32.emax + 1))) {
+ return [Number.NEGATIVE_INFINITY, kValue.f32.negative.min];
+ }
+
+ // Less than or equal to the lower overflow boundary
+ return [Number.NEGATIVE_INFINITY];
+}
+
+/**
+ * Calculate the valid roundings when quantizing to 16-bit floats
+ *
+ * TS/JS's number type is internally a f64, so quantization needs to occur when
+ * converting to f16 for WGSL. WGSL does not specify a specific rounding mode,
+ * so if a number is not precisely representable in 16-bits, but in the
+ * range, there are two possible valid quantizations. If it is precisely
+ * representable, there is only one valid quantization. This function calculates
+ * the valid roundings and returns them in an array.
+ *
+ * This function does not consider flushing mode, so subnormals are maintained.
+ * The caller is responsible to flushing before and after as appropriate.
+ *
+ * Out of bounds values need to consider how they interact with the overflow
+ * rules.
+ * * If a value is OOB but not too far out, an implementation may choose to round
+ * to nearest finite value or the correct infinity. This boundary is at
+ * 2^(f16.emax + 1) and -(2^(f16.emax + 1)) respectively.
+ * Values that are at or beyond these limits must be rounded towards the
+ * appropriate infinity.
+ *
+ * @param n number to be quantized
+ * @returns all of the acceptable roundings for quantizing to 16-bits in
+ * ascending order.
+ */
+export function correctlyRoundedF16(n) {
+ if (Number.isNaN(n)) {
+ return [n];
+ }
+
+ // Greater than or equal to the upper overflow boundry
+ if (n >= 2 ** (kValue.f16.emax + 1)) {
+ return [Number.POSITIVE_INFINITY];
+ }
+
+ // OOB, but less than the upper overflow boundary
+ if (n > kValue.f16.positive.max) {
+ return [kValue.f16.positive.max, Number.POSITIVE_INFINITY];
+ }
+
+ // f16 finite
+ if (n <= kValue.f16.positive.max && n >= kValue.f16.negative.min) {
+ const n_16 = quantizeToF16(n);
+ if (n === n_16) {
+ // n is precisely expressible as a f16, so should not be rounded
+ return [n];
+ }
+
+ if (n_16 > n) {
+ // n_16 rounded towards +inf, so is after n
+ const other = nextAfterF16(n_16, 'negative', 'no-flush');
+ return [other, n_16];
+ } else {
+ // n_16 rounded towards -inf, so is before n
+ const other = nextAfterF16(n_16, 'positive', 'no-flush');
+ return [n_16, other];
+ }
+ }
+
+ // OOB, but greater the lower overflow boundary
+ if (n > -(2 ** (kValue.f16.emax + 1))) {
+ return [Number.NEGATIVE_INFINITY, kValue.f16.negative.min];
+ }
+
+ // Less than or equal to the lower overflow boundary
+ return [Number.NEGATIVE_INFINITY];
+}
+
+/**
+ * Calculates WGSL frexp
+ *
+ * Splits val into a fraction and an exponent so that
+ * val = fraction * 2 ^ exponent.
+ * The fraction is 0.0 or its magnitude is in the range [0.5, 1.0).
+ *
+ * @param val the float to split
+ * @param trait the float type, f32 or f16 or f64
+ * @returns the results of splitting val
+ */
+export function frexp(val, trait) {
+ const buffer = new ArrayBuffer(8);
+ const dataView = new DataView(buffer);
+
+ // expBitCount and fractBitCount is the bitwidth of exponent and fractional part of the given FP type.
+ // expBias is the bias constant of exponent of the given FP type.
+ // Biased exponent (unsigned integer, i.e. the exponent part of float) = unbiased exponent (signed integer) + expBias.
+ let expBitCount, fractBitCount, expBias;
+ // To handle the exponent bits of given FP types (f16, f32, and f64), considering the highest 16
+ // bits is enough.
+ // expMaskForHigh16Bits indicates the exponent bitfield in the highest 16 bits of the given FP
+ // type, and targetExpBitsForHigh16Bits is the exponent bits that corresponding to unbiased
+ // exponent -1, i.e. the exponent bits when the FP values is in range [0.5, 1.0).
+ let expMaskForHigh16Bits, targetExpBitsForHigh16Bits;
+ // Helper function that store the given FP value into buffer as the given FP types
+ let setFloatToBuffer;
+ // Helper function that read back FP value from buffer as the given FP types
+ let getFloatFromBuffer;
+
+ let isFinite;
+ let isSubnormal;
+
+ if (trait === 'f32') {
+ // f32 bit pattern: s_eeeeeeee_fffffff_ffffffffffffffff
+ expBitCount = 8;
+ fractBitCount = 23;
+ expBias = 127;
+ // The exponent bitmask for high 16 bits of f32.
+ expMaskForHigh16Bits = 0x7f80;
+ // The target exponent bits is equal to those for f32 0.5 = 0x3f000000.
+ targetExpBitsForHigh16Bits = 0x3f00;
+ isFinite = isFiniteF32;
+ isSubnormal = isSubnormalNumberF32;
+ // Enforce big-endian so that offset 0 is highest byte.
+ setFloatToBuffer = (v) => dataView.setFloat32(0, v, false);
+ getFloatFromBuffer = () => dataView.getFloat32(0, false);
+ } else if (trait === 'f16') {
+ // f16 bit pattern: s_eeeee_ffffffffff
+ expBitCount = 5;
+ fractBitCount = 10;
+ expBias = 15;
+ // The exponent bitmask for 16 bits of f16.
+ expMaskForHigh16Bits = 0x7c00;
+ // The target exponent bits is equal to those for f16 0.5 = 0x3800.
+ targetExpBitsForHigh16Bits = 0x3800;
+ isFinite = isFiniteF16;
+ isSubnormal = isSubnormalNumberF16;
+ // Enforce big-endian so that offset 0 is highest byte.
+ setFloatToBuffer = (v) => setFloat16(dataView, 0, v, false);
+ getFloatFromBuffer = () => getFloat16(dataView, 0, false);
+ } else {
+ assert(trait === 'f64');
+ // f64 bit pattern: s_eeeeeeeeeee_ffff_ffffffffffffffffffffffffffffffffffffffffffffffff
+ expBitCount = 11;
+ fractBitCount = 52;
+ expBias = 1023;
+ // The exponent bitmask for 16 bits of f64.
+ expMaskForHigh16Bits = 0x7ff0;
+ // The target exponent bits is equal to those for f64 0.5 = 0x3fe0_0000_0000_0000.
+ targetExpBitsForHigh16Bits = 0x3fe0;
+ isFinite = Number.isFinite;
+ isSubnormal = isSubnormalNumberF64;
+ // Enforce big-endian so that offset 0 is highest byte.
+ setFloatToBuffer = (v) => dataView.setFloat64(0, v, false);
+ getFloatFromBuffer = () => dataView.getFloat64(0, false);
+ }
+ // Helper function that extract the unbiased exponent of the float in buffer.
+ const extractUnbiasedExpFromNormalFloatInBuffer = () => {
+ // Assert the float in buffer is finite normal float.
+ assert(isFinite(getFloatFromBuffer()) && !isSubnormal(getFloatFromBuffer()));
+ // Get the highest 16 bits of float as uint16, which can contain the whole exponent part for both f16, f32, and f64.
+ const high16BitsAsUint16 = dataView.getUint16(0, false);
+ // Return the unbiased exp by masking, shifting and unbiasing.
+ return ((high16BitsAsUint16 & expMaskForHigh16Bits) >> 16 - 1 - expBitCount) - expBias;
+ };
+ // Helper function that modify the exponent of float in buffer to make it in range [0.5, 1.0).
+ // By setting the unbiased exponent to -1, the fp value will be in range 2**-1 * [1.0, 2.0), i.e. [0.5, 1.0).
+ const modifyExpOfNormalFloatInBuffer = () => {
+ // Assert the float in buffer is finite normal float.
+ assert(isFinite(getFloatFromBuffer()) && !isSubnormal(getFloatFromBuffer()));
+ // Get the highest 16 bits of float as uint16, which contains the whole exponent part for both f16, f32, and f64.
+ const high16BitsAsUint16 = dataView.getUint16(0, false);
+ // Modify the exponent bits.
+ const modifiedHigh16Bits =
+ high16BitsAsUint16 & ~expMaskForHigh16Bits | targetExpBitsForHigh16Bits;
+ // Set back to buffer
+ dataView.setUint16(0, modifiedHigh16Bits, false);
+ };
+
+ // +/- 0.0
+ if (val === 0) {
+ return { fract: val, exp: 0 };
+ }
+ // NaN and Inf
+ if (!isFinite(val)) {
+ return { fract: val, exp: 0 };
+ }
+
+ setFloatToBuffer(val);
+ // Don't use val below. Use helper functions working with buffer instead.
+
+ let exp = 0;
+ // Normailze the value if it is subnormal. Increase the exponent by multiplying a subnormal value
+ // with 2**fractBitCount will result in a finite normal FP value of the given FP type.
+ if (isSubnormal(getFloatFromBuffer())) {
+ setFloatToBuffer(getFloatFromBuffer() * 2 ** fractBitCount);
+ exp = -fractBitCount;
+ }
+ // A normal FP value v is represented as v = ((-1)**s)*(2**(unbiased exponent))*f, where f is in
+ // range [1.0, 2.0). By moving a factor 2 from f to exponent, we have
+ // v = ((-1)**s)*(2**(unbiased exponent + 1))*(f / 2), where (f / 2) is in range [0.5, 1.0), so
+ // the exp = (unbiased exponent + 1) and fract = ((-1)**s)*(f / 2) is what we expect to get from
+ // frexp function. Note that fract and v only differs in exponent bitfield as long as v is normal.
+ // Calc the result exp by getting the unbiased float exponent and plus 1.
+ exp += extractUnbiasedExpFromNormalFloatInBuffer() + 1;
+ // Modify the exponent of float in buffer to make it be in range [0.5, 1.0) to get fract.
+ modifyExpOfNormalFloatInBuffer();
+
+ return { fract: getFloatFromBuffer(), exp };
+}
+
+/**
+ * Calculates the linear interpolation between two values of a given fractional.
+ *
+ * If |t| is 0, |a| is returned, if |t| is 1, |b| is returned, otherwise
+ * interpolation/extrapolation equivalent to a + t(b - a) is performed.
+ *
+ * Numerical stable version is adapted from http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0811r2.html
+ */
+export function lerp(a, b, t) {
+ if (!Number.isFinite(a) || !Number.isFinite(b)) {
+ return Number.NaN;
+ }
+
+ if (a <= 0.0 && b >= 0.0 || a >= 0.0 && b <= 0.0) {
+ return t * b + (1 - t) * a;
+ }
+
+ if (t === 1.0) {
+ return b;
+ }
+
+ const x = a + t * (b - a);
+ return t > 1.0 === b > a ? Math.max(b, x) : Math.min(b, x);
+}
+
+/**
+ * Version of lerp that operates on bigint values
+ *
+ * lerp was not made into a generic or to take in (number|bigint), because that
+ * introduces a bunch of complexity overhead related to type differentiation
+ */
+export function lerpBigInt(a, b, idx, steps) {
+ assert(Math.trunc(idx) === idx);
+ assert(Math.trunc(steps) === steps);
+
+ // This constrains t to [0.0, 1.0]
+ assert(idx >= 0);
+ assert(steps > 0);
+ assert(idx < steps);
+
+ if (steps === 1) {
+ return a;
+ }
+ if (idx === 0) {
+ return a;
+ }
+ if (idx === steps - 1) {
+ return b;
+ }
+
+ const min = (x, y) => {
+ return x < y ? x : y;
+ };
+ const max = (x, y) => {
+ return x > y ? x : y;
+ };
+
+ // For number the variable t is used, there t = idx / (steps - 1),
+ // but that is a fraction on [0, 1], so becomes either 0 or 1 when converted
+ // to bigint, so need to expand things out.
+ const big_idx = BigInt(idx);
+ const big_steps = BigInt(steps);
+ if (a <= 0n && b >= 0n || a >= 0n && b <= 0n) {
+ return b * big_idx / (big_steps - 1n) + (a - a * big_idx / (big_steps - 1n));
+ }
+
+ const x = a + b * big_idx / (big_steps - 1n) - a * big_idx / (big_steps - 1n);
+ return !(b > a) ? max(b, x) : min(b, x);
+}
+
+/** @returns a linear increasing range of numbers. */
+export function linearRange(a, b, num_steps) {
+ if (num_steps <= 0) {
+ return [];
+ }
+
+ // Avoid division by 0
+ if (num_steps === 1) {
+ return [a];
+ }
+
+ return Array.from(Array(num_steps).keys()).map((i) => lerp(a, b, i / (num_steps - 1)));
+}
+
+/**
+ * Version of linearRange that operates on bigint values
+ *
+ * linearRange was not made into a generic or to take in (number|bigint),
+ * because that introduces a bunch of complexity overhead related to type
+ * differentiation
+ */
+export function linearRangeBigInt(a, b, num_steps) {
+ if (num_steps <= 0) {
+ return [];
+ }
+
+ // Avoid division by 0
+ if (num_steps === 1) {
+ return [a];
+ }
+
+ return Array.from(Array(num_steps).keys()).map((i) => lerpBigInt(a, b, i, num_steps));
+}
+
+/**
+ * @returns a non-linear increasing range of numbers, with a bias towards the beginning.
+ *
+ * Generates a linear range on [0,1] with |num_steps|, then squares all the values to make the curve be quadratic,
+ * thus biasing towards 0, but remaining on the [0, 1] range.
+ * This biased range is then scaled to the desired range using lerp.
+ * Different curves could be generated by changing c, where greater values of c will bias more towards 0.
+ */
+export function biasedRange(a, b, num_steps) {
+ const c = 2;
+ if (num_steps <= 0) {
+ return [];
+ }
+
+ // Avoid division by 0
+ if (num_steps === 1) {
+ return [a];
+ }
+
+ return Array.from(Array(num_steps).keys()).map((i) => lerp(a, b, Math.pow(i / (num_steps - 1), c)));
+}
+
+/**
+ * @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.
+ * Zero is included.
+ *
+ * Numbers are generated via taking a linear spread of the bit field representations of the values in each region. This
+ * means that number of precise f32 values between each returned value in a region should be about the same. This allows
+ * 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.
+ *
+ * @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(
+counts =
+
+
+
+
+{ pos_sub: 10, pos_norm: 50 })
+{
+ counts.neg_norm = counts.neg_norm === undefined ? counts.pos_norm : counts.neg_norm;
+ counts.neg_sub = counts.neg_sub === undefined ? counts.pos_sub : counts.neg_sub;
+
+ // Generating bit fields first and then converting to f32, so that the spread across the possible f32 values is more
+ // even. Generating against the bounds of f32 values directly results in the values being extremely biased towards the
+ // extremes, since they are so much larger.
+ const bit_fields = [
+ ...linearRange(kBit.f32.negative.min, kBit.f32.negative.max, counts.neg_norm),
+ ...linearRange(
+ kBit.f32.negative.subnormal.min,
+ kBit.f32.negative.subnormal.max,
+ counts.neg_sub
+ ),
+ // -0.0
+ 0x80000000,
+ // +0.0
+ 0,
+ ...linearRange(
+ kBit.f32.positive.subnormal.min,
+ kBit.f32.positive.subnormal.max,
+ counts.pos_sub
+ ),
+ ...linearRange(kBit.f32.positive.min, kBit.f32.positive.max, counts.pos_norm)].
+ map(Math.trunc);
+ return bit_fields.map(reinterpretU32AsF32);
+}
+
+/**
+ * @returns an ascending sorted array of numbers.
+ *
+ * The numbers returned are based on the `full32Range` as described above. The difference comes depending
+ * on the `source` parameter. If the `source` is `const` then the numbers will be restricted to be
+ * in the range `[low, high]`. This allows filtering out a set of `f32` values which are invalid for
+ * const-evaluation but are needed to test the non-const implementation.
+ *
+ * @param source the input source for the test. If the `source` is `const` then the return will be filtered
+ * @param low the lowest f32 value to permit when filtered
+ * @param high the highest f32 value to permit when filtered
+ */
+export function sourceFilteredF32Range(source, low, high) {
+ return fullF32Range().filter((x) => source !== 'const' || x >= low && x <= high);
+}
+
+/**
+ * @returns an ascending sorted array of numbers spread over the entire range of 16-bit floats
+ *
+ * Numbers are divided into 4 regions: negative normals, negative subnormals, positive subnormals & positive normals.
+ * Zero is included.
+ *
+ * Numbers are generated via taking a linear spread of the bit field representations of the values in each region. This
+ * means that number of precise f16 values between each returned value in a region should be about the same. This allows
+ * 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.
+ *
+ * @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(
+counts =
+
+
+
+
+{ pos_sub: 10, pos_norm: 50 })
+{
+ counts.neg_norm = counts.neg_norm === undefined ? counts.pos_norm : counts.neg_norm;
+ counts.neg_sub = counts.neg_sub === undefined ? counts.pos_sub : counts.neg_sub;
+
+ // Generating bit fields first and then converting to f16, so that the spread across the possible f16 values is more
+ // even. Generating against the bounds of f16 values directly results in the values being extremely biased towards the
+ // extremes, since they are so much larger.
+ const bit_fields = [
+ ...linearRange(kBit.f16.negative.min, kBit.f16.negative.max, counts.neg_norm),
+ ...linearRange(
+ kBit.f16.negative.subnormal.min,
+ kBit.f16.negative.subnormal.max,
+ counts.neg_sub
+ ),
+ // -0.0
+ 0x8000,
+ // +0.0
+ 0,
+ ...linearRange(
+ kBit.f16.positive.subnormal.min,
+ kBit.f16.positive.subnormal.max,
+ counts.pos_sub
+ ),
+ ...linearRange(kBit.f16.positive.min, kBit.f16.positive.max, counts.pos_norm)].
+ map(Math.trunc);
+ return bit_fields.map(reinterpretU16AsF16);
+}
+
+/**
+ * @returns an ascending sorted array of numbers spread over the entire range of 64-bit floats
+ *
+ * Numbers are divided into 4 regions: negative normals, negative subnormals, positive subnormals & positive normals.
+ * Zero is included.
+ *
+ * Numbers are generated via taking a linear spread of the bit field representations of the values in each region. This
+ * means that number of precise f64 values between each returned value in a region should be about the same. This allows
+ * 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.
+ *
+ * @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(
+counts =
+
+
+
+
+{ pos_sub: 10, pos_norm: 50 })
+{
+ counts.neg_norm = counts.neg_norm === undefined ? counts.pos_norm : counts.neg_norm;
+ counts.neg_sub = counts.neg_sub === undefined ? counts.pos_sub : counts.neg_sub;
+
+ // Generating bit fields first and then converting to f64, so that the spread across the possible f64 values is more
+ // even. Generating against the bounds of f64 values directly results in the values being extremely biased towards the
+ // extremes, since they are so much larger.
+ const bit_fields = [
+ ...linearRangeBigInt(kBit.f64.negative.min, kBit.f64.negative.max, counts.neg_norm),
+ ...linearRangeBigInt(
+ kBit.f64.negative.subnormal.min,
+ kBit.f64.negative.subnormal.max,
+ counts.neg_sub
+ ),
+ // -0.0
+ 0x8000_0000_0000_0000n,
+ // +0.0
+ 0n,
+ ...linearRangeBigInt(
+ kBit.f64.positive.subnormal.min,
+ kBit.f64.positive.subnormal.max,
+ counts.pos_sub
+ ),
+ ...linearRangeBigInt(kBit.f64.positive.min, kBit.f64.positive.max, counts.pos_norm)];
+
+ return bit_fields.map(reinterpretU64AsF64);
+}
+
+/**
+ * @returns an ascending sorted array of f64 values spread over specific range of f64 normal floats
+ *
+ * Numbers are divided into 4 regions: negative 64-bit normals, negative 64-bit subnormals, positive 64-bit subnormals &
+ * positive 64-bit normals.
+ * Zero is included.
+ *
+ * Numbers are generated via taking a linear spread of the bit field representations of the values in each region. This
+ * means that number of precise f64 values between each returned value in a region should be about the same. This allows
+ * for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the range.
+ *
+ * @param begin a negative f64 normal float value
+ * @param end a positive f64 normal float value
+ * @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(
+begin,
+end,
+counts = {
+ pos_sub: 10,
+ pos_norm: 50
+})
+{
+ assert(
+ begin <= kValue.f64.negative.max,
+ `Beginning of range ${begin} must be negative f64 normal`
+ );
+ assert(end >= kValue.f64.positive.min, `Ending of range ${end} must be positive f64 normal`);
+
+ counts.neg_norm = counts.neg_norm === undefined ? counts.pos_norm : counts.neg_norm;
+ counts.neg_sub = counts.neg_sub === undefined ? counts.pos_sub : counts.neg_sub;
+
+ const u64_begin = reinterpretF64AsU64(begin);
+ const u64_end = reinterpretF64AsU64(end);
+ // Generating bit fields first and then converting to f64, so that the spread across the possible f64 values is more
+ // even. Generating against the bounds of f64 values directly results in the values being extremely biased towards the
+ // extremes, since they are so much larger.
+ const bit_fields = [
+ ...linearRangeBigInt(u64_begin, kBit.f64.negative.max, counts.neg_norm),
+ ...linearRangeBigInt(
+ kBit.f64.negative.subnormal.min,
+ kBit.f64.negative.subnormal.max,
+ counts.neg_sub
+ ),
+ // -0.0
+ 0x8000_0000_0000_0000n,
+ // +0.0
+ 0n,
+ ...linearRangeBigInt(
+ kBit.f64.positive.subnormal.min,
+ kBit.f64.positive.subnormal.max,
+ counts.pos_sub
+ ),
+ ...linearRangeBigInt(kBit.f64.positive.min, u64_end, counts.pos_norm)];
+
+ return bit_fields.map(reinterpretU64AsF64);
+}
+
+/** Short list of i32 values of interest to test against */
+const kInterestingI32Values = [
+kValue.i32.negative.max,
+Math.trunc(kValue.i32.negative.max / 2),
+-256,
+-10,
+-1,
+0,
+1,
+10,
+256,
+Math.trunc(kValue.i32.positive.max / 2),
+kValue.i32.positive.max];
+
+
+/** @returns minimal i32 values that cover the entire range of i32 behaviours
+ *
+ * This is used instead of fullI32Range when the number of test cases being
+ * generated is a super linear function of the length of i32 values which is
+ * leading to time outs.
+ */
+export function sparseI32Range() {
+ return kInterestingI32Values;
+}
+
+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]]
+ ),
+ 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]]
+ )
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting i32
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting i32 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 i32 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 vectorI32Range(dim) {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorI32Range only accepts dimensions 2, 3, and 4');
+ return kVectorI32Values[dim];
+}
+
+/**
+ * @returns an ascending sorted array of numbers spread over the entire range of 32-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 fullI32Range(
+counts =
+
+
+{ positive: 50 })
+{
+ counts.negative = counts.negative === undefined ? counts.positive : counts.negative;
+ return [
+ ...biasedRange(kValue.i32.negative.min, -1, counts.negative),
+ 0,
+ ...biasedRange(1, kValue.i32.positive.max, counts.positive)].
+ map(Math.trunc);
+}
+
+/** Short list of u32 values of interest to test against */
+const kInterestingU32Values = [
+0,
+1,
+10,
+256,
+Math.trunc(kValue.u32.max / 2),
+kValue.u32.max];
+
+
+/** @returns minimal u32 values that cover the entire range of u32 behaviours
+ *
+ * This is used instead of fullU32Range when the number of test cases being
+ * generated is a super linear function of the length of u32 values which is
+ * leading to time outs.
+ */
+export function sparseU32Range() {
+ return kInterestingU32Values;
+}
+
+const kVectorU32Values = {
+ 2: kInterestingU32Values.flatMap((f) => [
+ [f, 1],
+ [1, f]]
+ ),
+ 3: kInterestingU32Values.flatMap((f) => [
+ [f, 1, 2],
+ [1, f, 2],
+ [1, 2, f]]
+ ),
+ 4: kInterestingU32Values.flatMap((f) => [
+ [f, 1, 2, 3],
+ [1, f, 2, 3],
+ [1, 2, f, 3],
+ [1, 2, 3, f]]
+ )
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting u32
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting u32 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 u32 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 vectorU32Range(dim) {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorU32Range only accepts dimensions 2, 3, and 4');
+ return kVectorU32Values[dim];
+}
+
+/**
+ * @returns an ascending sorted array of numbers spread over the entire range of 32-bit unsigned ints
+ *
+ * Numbers are biased towards 0, and 0 is included in the range.
+ *
+ * @param count number of entries to include in the range, in addition to 0, must be greater than 0, defaults to 50
+ */
+export function fullU32Range(count = 50) {
+ return [0, ...biasedRange(1, kValue.u32.max, count)].map(Math.trunc);
+}
+
+/** Short list of f32 values of interest to test against */
+const kInterestingF32Values = [
+kValue.f32.negative.min,
+-10.0,
+-1.0,
+-0.125,
+kValue.f32.negative.max,
+kValue.f32.negative.subnormal.min,
+kValue.f32.negative.subnormal.max,
+-0.0,
+0.0,
+kValue.f32.positive.subnormal.min,
+kValue.f32.positive.subnormal.max,
+kValue.f32.positive.min,
+0.125,
+1.0,
+10.0,
+kValue.f32.positive.max];
+
+
+/** @returns minimal f32 values that cover the entire range of f32 behaviours
+ *
+ * Has specially selected values that cover edge cases, normals, and subnormals.
+ * This is used instead of fullF32Range when the number of test cases being
+ * generated is a super linear function of the length of f32 values which is
+ * leading to time outs.
+ *
+ * These values have been chosen to attempt to test the widest range of f32
+ * behaviours in the lowest number of entries, so may potentially miss function
+ * 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() {
+ return kInterestingF32Values;
+}
+
+const kVectorF32Values = {
+ 2: sparseF32Range().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]]
+ ),
+ 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]]
+ )
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting float
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting float 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 f32 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 vectorF32Range(dim) {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorF32Range only accepts dimensions 2, 3, and 4');
+ return kVectorF32Values[dim];
+}
+
+const kSparseVectorF32Values = {
+ 2: sparseF32Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseF32Range().map((f, idx) => [
+ idx % 3 === 0 ? f : idx,
+ idx % 3 === 1 ? f : -idx,
+ idx % 3 === 2 ? f : idx]
+ ),
+ 4: sparseF32Range().map((f, idx) => [
+ idx % 4 === 0 ? f : idx,
+ idx % 4 === 1 ? f : -idx,
+ idx % 4 === 2 ? f : idx,
+ idx % 4 === 3 ? f : -idx]
+ )
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting float
+ * values.
+ *
+ * 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.
+ */
+export function sparseVectorF32Range(dim) {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorF32Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorF32Values[dim];
+}
+
+const kSparseMatrixF32Values = {
+ 2: {
+ 2: kInterestingF32Values.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx]]
+ ),
+ 3: kInterestingF32Values.map((f, idx) => [
+ [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx, idx % 6 === 2 ? f : idx],
+ [idx % 6 === 3 ? f : -idx, idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx]]
+ ),
+ 4: kInterestingF32Values.map((f, idx) => [
+ [
+ idx % 8 === 0 ? f : idx,
+ idx % 8 === 1 ? f : -idx,
+ idx % 8 === 2 ? f : idx,
+ idx % 8 === 3 ? f : -idx],
+
+ [
+ idx % 8 === 4 ? f : -idx,
+ idx % 8 === 5 ? f : idx,
+ idx % 8 === 6 ? f : -idx,
+ idx % 8 === 7 ? f : idx]]
+
+ )
+ },
+ 3: {
+ 2: kInterestingF32Values.map((f, idx) => [
+ [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx],
+ [idx % 6 === 2 ? f : -idx, idx % 6 === 3 ? f : idx],
+ [idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx]]
+ ),
+ 3: kInterestingF32Values.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: kInterestingF32Values.map((f, idx) => [
+ [
+ idx % 12 === 0 ? f : idx,
+ idx % 12 === 1 ? f : -idx,
+ idx % 12 === 2 ? f : idx,
+ idx % 12 === 3 ? f : -idx],
+
+ [
+ idx % 12 === 4 ? f : -idx,
+ idx % 12 === 5 ? f : idx,
+ idx % 12 === 6 ? f : -idx,
+ idx % 12 === 7 ? f : idx],
+
+ [
+ idx % 12 === 8 ? f : idx,
+ idx % 12 === 9 ? f : -idx,
+ idx % 12 === 10 ? f : idx,
+ idx % 12 === 11 ? f : -idx]]
+
+ )
+ },
+ 4: {
+ 2: kInterestingF32Values.map((f, idx) => [
+ [idx % 8 === 0 ? f : idx, idx % 8 === 1 ? f : -idx],
+ [idx % 8 === 2 ? f : -idx, idx % 8 === 3 ? f : idx],
+ [idx % 8 === 4 ? f : idx, idx % 8 === 5 ? f : -idx],
+ [idx % 8 === 6 ? f : -idx, idx % 8 === 7 ? f : idx]]
+ ),
+ 3: kInterestingF32Values.map((f, idx) => [
+ [idx % 12 === 0 ? f : idx, idx % 12 === 1 ? f : -idx, idx % 12 === 2 ? f : idx],
+ [idx % 12 === 3 ? f : -idx, idx % 12 === 4 ? f : idx, idx % 12 === 5 ? f : -idx],
+ [idx % 12 === 6 ? f : idx, idx % 12 === 7 ? f : -idx, idx % 12 === 8 ? f : idx],
+ [idx % 12 === 9 ? f : -idx, idx % 12 === 10 ? f : idx, idx % 12 === 11 ? f : -idx]]
+ ),
+ 4: kInterestingF32Values.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx],
+
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx],
+
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx],
+
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx]]
+
+ )
+ }
+};
+
+/**
+ * Returns a minimal set of matrices, indexed by dimension containing interesting
+ * float values.
+ *
+ * This is the matrix analogue of `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.
+ */
+export function sparseMatrixF32Range(c, r) {
+ assert(
+ c === 2 || c === 3 || c === 4,
+ 'sparseMatrixF32Range only accepts column counts of 2, 3, and 4'
+ );
+ assert(
+ r === 2 || r === 3 || r === 4,
+ 'sparseMatrixF32Range only accepts row counts of 2, 3, and 4'
+ );
+ return kSparseMatrixF32Values[c][r];
+}
+
+/** Short list of f16 values of interest to test against */
+const kInterestingF16Values = [
+kValue.f16.negative.min,
+-10.0,
+-1.0,
+-0.125,
+kValue.f16.negative.max,
+kValue.f16.negative.subnormal.min,
+kValue.f16.negative.subnormal.max,
+-0.0,
+0.0,
+kValue.f16.positive.subnormal.min,
+kValue.f16.positive.subnormal.max,
+kValue.f16.positive.min,
+0.125,
+1.0,
+10.0,
+kValue.f16.positive.max];
+
+
+/** @returns minimal f16 values that cover the entire range of f16 behaviours
+ *
+ * Has specially selected values that cover edge cases, normals, and subnormals.
+ * This is used instead of fullF16Range when the number of test cases being
+ * generated is a super linear function of the length of f16 values which is
+ * leading to time outs.
+ *
+ * These values have been chosen to attempt to test the widest range of f16
+ * behaviours in the lowest number of entries, so may potentially miss function
+ * 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() {
+ return kInterestingF16Values;
+}
+
+const kVectorF16Values = {
+ 2: sparseF16Range().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]]
+ ),
+ 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]]
+ )
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting f16
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting float 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 f16 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 vectorF16Range(dim) {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorF16Range only accepts dimensions 2, 3, and 4');
+ return kVectorF16Values[dim];
+}
+
+const kSparseVectorF16Values = {
+ 2: sparseF16Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseF16Range().map((f, idx) => [
+ idx % 3 === 0 ? f : idx,
+ idx % 3 === 1 ? f : -idx,
+ idx % 3 === 2 ? f : idx]
+ ),
+ 4: sparseF16Range().map((f, idx) => [
+ idx % 4 === 0 ? f : idx,
+ idx % 4 === 1 ? f : -idx,
+ idx % 4 === 2 ? f : idx,
+ idx % 4 === 3 ? f : -idx]
+ )
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting f16
+ * values.
+ *
+ * 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.
+ */
+export function sparseVectorF16Range(dim) {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorF16Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorF16Values[dim];
+}
+
+const kSparseMatrixF16Values = {
+ 2: {
+ 2: kInterestingF16Values.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx]]
+ ),
+ 3: kInterestingF16Values.map((f, idx) => [
+ [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx, idx % 6 === 2 ? f : idx],
+ [idx % 6 === 3 ? f : -idx, idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx]]
+ ),
+ 4: kInterestingF16Values.map((f, idx) => [
+ [
+ idx % 8 === 0 ? f : idx,
+ idx % 8 === 1 ? f : -idx,
+ idx % 8 === 2 ? f : idx,
+ idx % 8 === 3 ? f : -idx],
+
+ [
+ idx % 8 === 4 ? f : -idx,
+ idx % 8 === 5 ? f : idx,
+ idx % 8 === 6 ? f : -idx,
+ idx % 8 === 7 ? f : idx]]
+
+ )
+ },
+ 3: {
+ 2: kInterestingF16Values.map((f, idx) => [
+ [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx],
+ [idx % 6 === 2 ? f : -idx, idx % 6 === 3 ? f : idx],
+ [idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx]]
+ ),
+ 3: kInterestingF16Values.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: kInterestingF16Values.map((f, idx) => [
+ [
+ idx % 12 === 0 ? f : idx,
+ idx % 12 === 1 ? f : -idx,
+ idx % 12 === 2 ? f : idx,
+ idx % 12 === 3 ? f : -idx],
+
+ [
+ idx % 12 === 4 ? f : -idx,
+ idx % 12 === 5 ? f : idx,
+ idx % 12 === 6 ? f : -idx,
+ idx % 12 === 7 ? f : idx],
+
+ [
+ idx % 12 === 8 ? f : idx,
+ idx % 12 === 9 ? f : -idx,
+ idx % 12 === 10 ? f : idx,
+ idx % 12 === 11 ? f : -idx]]
+
+ )
+ },
+ 4: {
+ 2: kInterestingF16Values.map((f, idx) => [
+ [idx % 8 === 0 ? f : idx, idx % 8 === 1 ? f : -idx],
+ [idx % 8 === 2 ? f : -idx, idx % 8 === 3 ? f : idx],
+ [idx % 8 === 4 ? f : idx, idx % 8 === 5 ? f : -idx],
+ [idx % 8 === 6 ? f : -idx, idx % 8 === 7 ? f : idx]]
+ ),
+ 3: kInterestingF16Values.map((f, idx) => [
+ [idx % 12 === 0 ? f : idx, idx % 12 === 1 ? f : -idx, idx % 12 === 2 ? f : idx],
+ [idx % 12 === 3 ? f : -idx, idx % 12 === 4 ? f : idx, idx % 12 === 5 ? f : -idx],
+ [idx % 12 === 6 ? f : idx, idx % 12 === 7 ? f : -idx, idx % 12 === 8 ? f : idx],
+ [idx % 12 === 9 ? f : -idx, idx % 12 === 10 ? f : idx, idx % 12 === 11 ? f : -idx]]
+ ),
+ 4: kInterestingF16Values.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx],
+
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx],
+
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx],
+
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx]]
+
+ )
+ }
+};
+
+/**
+ * Returns a minimal set of matrices, indexed by dimension containing interesting
+ * f16 values.
+ *
+ * This is the matrix analogue of `sparseVectorF16Range`, so it is producing a
+ * minimal coverage set of matrices that test all of the interesting f16 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 sparseF16 are guaranteed to be tested, but
+ * not in every position.
+ */
+export function sparseMatrixF16Range(c, r) {
+ assert(
+ c === 2 || c === 3 || c === 4,
+ 'sparseMatrixF16Range only accepts column counts of 2, 3, and 4'
+ );
+ assert(
+ r === 2 || r === 3 || r === 4,
+ 'sparseMatrixF16Range only accepts row counts of 2, 3, and 4'
+ );
+ return kSparseMatrixF16Values[c][r];
+}
+
+/** Short list of f64 values of interest to test against */
+const kInterestingF64Values = [
+kValue.f64.negative.min,
+-10.0,
+-1.0,
+-0.125,
+kValue.f64.negative.max,
+kValue.f64.negative.subnormal.min,
+kValue.f64.negative.subnormal.max,
+-0.0,
+0.0,
+kValue.f64.positive.subnormal.min,
+kValue.f64.positive.subnormal.max,
+kValue.f64.positive.min,
+0.125,
+1.0,
+10.0,
+kValue.f64.positive.max];
+
+
+/** @returns minimal F64 values that cover the entire range of F64 behaviours
+ *
+ * Has specially selected values that cover edge cases, normals, and subnormals.
+ * This is used instead of fullF64Range when the number of test cases being
+ * generated is a super linear function of the length of F64 values which is
+ * leading to time outs.
+ *
+ * These values have been chosen to attempt to test the widest range of F64
+ * behaviours in the lowest number of entries, so may potentially miss function
+ * 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() {
+ return kInterestingF64Values;
+}
+
+const kVectorF64Values = {
+ 2: sparseF64Range().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]]
+ ),
+ 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]]
+ )
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting float
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting float 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 F64 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 vectorF64Range(dim) {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorF64Range only accepts dimensions 2, 3, and 4');
+ return kVectorF64Values[dim];
+}
+
+const kSparseVectorF64Values = {
+ 2: sparseF64Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseF64Range().map((f, idx) => [
+ idx % 3 === 0 ? f : idx,
+ idx % 3 === 1 ? f : -idx,
+ idx % 3 === 2 ? f : idx]
+ ),
+ 4: sparseF64Range().map((f, idx) => [
+ idx % 4 === 0 ? f : idx,
+ idx % 4 === 1 ? f : -idx,
+ idx % 4 === 2 ? f : idx,
+ idx % 4 === 3 ? f : -idx]
+ )
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting f64
+ * values.
+ *
+ * This is an even more stripped down version of `vectorF64Range` for when
+ * pairs of vectors are being tested.
+ * All the interesting floats from sparseF64 are guaranteed to be tested, but
+ * not in every position.
+ */
+export function sparseVectorF64Range(dim) {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorF64Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorF64Values[dim];
+}
+
+const kSparseMatrixF64Values = {
+ 2: {
+ 2: kInterestingF64Values.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx]]
+ ),
+ 3: kInterestingF64Values.map((f, idx) => [
+ [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx, idx % 6 === 2 ? f : idx],
+ [idx % 6 === 3 ? f : -idx, idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx]]
+ ),
+ 4: kInterestingF64Values.map((f, idx) => [
+ [
+ idx % 8 === 0 ? f : idx,
+ idx % 8 === 1 ? f : -idx,
+ idx % 8 === 2 ? f : idx,
+ idx % 8 === 3 ? f : -idx],
+
+ [
+ idx % 8 === 4 ? f : -idx,
+ idx % 8 === 5 ? f : idx,
+ idx % 8 === 6 ? f : -idx,
+ idx % 8 === 7 ? f : idx]]
+
+ )
+ },
+ 3: {
+ 2: kInterestingF64Values.map((f, idx) => [
+ [idx % 6 === 0 ? f : idx, idx % 6 === 1 ? f : -idx],
+ [idx % 6 === 2 ? f : -idx, idx % 6 === 3 ? f : idx],
+ [idx % 6 === 4 ? f : idx, idx % 6 === 5 ? f : -idx]]
+ ),
+ 3: kInterestingF64Values.map((f, idx) => [
+ [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
+ [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
+ [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx]]
+ ),
+ 4: kInterestingF64Values.map((f, idx) => [
+ [
+ idx % 12 === 0 ? f : idx,
+ idx % 12 === 1 ? f : -idx,
+ idx % 12 === 2 ? f : idx,
+ idx % 12 === 3 ? f : -idx],
+
+ [
+ idx % 12 === 4 ? f : -idx,
+ idx % 12 === 5 ? f : idx,
+ idx % 12 === 6 ? f : -idx,
+ idx % 12 === 7 ? f : idx],
+
+ [
+ idx % 12 === 8 ? f : idx,
+ idx % 12 === 9 ? f : -idx,
+ idx % 12 === 10 ? f : idx,
+ idx % 12 === 11 ? f : -idx]]
+
+ )
+ },
+ 4: {
+ 2: kInterestingF64Values.map((f, idx) => [
+ [idx % 8 === 0 ? f : idx, idx % 8 === 1 ? f : -idx],
+ [idx % 8 === 2 ? f : -idx, idx % 8 === 3 ? f : idx],
+ [idx % 8 === 4 ? f : idx, idx % 8 === 5 ? f : -idx],
+ [idx % 8 === 6 ? f : -idx, idx % 8 === 7 ? f : idx]]
+ ),
+ 3: kInterestingF64Values.map((f, idx) => [
+ [idx % 12 === 0 ? f : idx, idx % 12 === 1 ? f : -idx, idx % 12 === 2 ? f : idx],
+ [idx % 12 === 3 ? f : -idx, idx % 12 === 4 ? f : idx, idx % 12 === 5 ? f : -idx],
+ [idx % 12 === 6 ? f : idx, idx % 12 === 7 ? f : -idx, idx % 12 === 8 ? f : idx],
+ [idx % 12 === 9 ? f : -idx, idx % 12 === 10 ? f : idx, idx % 12 === 11 ? f : -idx]]
+ ),
+ 4: kInterestingF64Values.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx],
+
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx],
+
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx],
+
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx]]
+
+ )
+ }
+};
+
+/**
+ * Returns a minimal set of matrices, indexed by dimension containing interesting
+ * float values.
+ *
+ * This is the matrix analogue of `sparseVectorF64Range`, so it is producing a
+ * minimal coverage set of matrices that test all the interesting f64 values.
+ * There is not a more expansive set of matrices, since matrices are even more
+ * expensive than vectors for increasing runtime with coverage.
+ *
+ * All the interesting floats from sparseF64 are guaranteed to be tested, but
+ * not in every position.
+ */
+export function sparseMatrixF64Range(c, r) {
+ assert(
+ c === 2 || c === 3 || c === 4,
+ 'sparseMatrixF64Range only accepts column counts of 2, 3, and 4'
+ );
+ assert(
+ r === 2 || r === 3 || r === 4,
+ 'sparseMatrixF64Range only accepts row counts of 2, 3, and 4'
+ );
+ return kSparseMatrixF64Values[c][r];
+}
+
+/**
+ * @returns the result matrix in Array<Array<number>> type.
+ *
+ * Matrix multiplication. A is m x n and B is n x p. Returns
+ * m x p result.
+ */
+// A is m x n. B is n x p. product is m x p.
+export function multiplyMatrices(
+A,
+B)
+{
+ assert(A.length > 0 && B.length > 0 && B[0].length > 0 && A[0].length === B.length);
+ const product = new Array(A.length);
+ for (let i = 0; i < product.length; ++i) {
+ product[i] = new Array(B[0].length).fill(0);
+ }
+
+ for (let m = 0; m < A.length; ++m) {
+ for (let p = 0; p < B[0].length; ++p) {
+ for (let n = 0; n < B.length; ++n) {
+ product[m][p] += A[m][n] * B[n][p];
+ }
+ }
+ }
+
+ return product;
+}
+
+/** Sign-extend the `bits`-bit number `n` to a 32-bit signed integer. */
+export function signExtend(n, bits) {
+ const shift = 32 - bits;
+ return n << shift >> shift;
+}
+
+
+
+
+
+/** @returns the closest 32-bit floating point value to the input */
+export function quantizeToF32(num) {
+ return Math.fround(num);
+}
+
+/** @returns the closest 16-bit floating point value to the input */
+export function quantizeToF16(num) {
+ return hfround(num);
+}
+
+/**
+ * @returns the closest 32-bit signed integer value to the input, rounding
+ * towards 0, if not already an integer
+ */
+export function quantizeToI32(num) {
+ if (num >= kValue.i32.positive.max) {
+ return kValue.i32.positive.max;
+ }
+ if (num <= kValue.i32.negative.min) {
+ return kValue.i32.negative.min;
+ }
+ return Math.trunc(num);
+}
+
+/**
+ * @returns the closest 32-bit unsigned integer value to the input, rounding
+ * towards 0, if not already an integer
+ */
+export function quantizeToU32(num) {
+ if (num >= kValue.u32.max) {
+ return kValue.u32.max;
+ }
+ if (num <= 0) {
+ return 0;
+ }
+ return Math.trunc(num);
+}
+
+/** @returns whether the number is an integer and a power of two */
+export function isPowerOfTwo(n) {
+ if (!Number.isInteger(n)) {
+ return false;
+ }
+ return n !== 0 && (n & n - 1) === 0;
+}
+
+/** @returns the Greatest Common Divisor (GCD) of the inputs */
+export function gcd(a, b) {
+ assert(Number.isInteger(a) && a > 0);
+ assert(Number.isInteger(b) && b > 0);
+
+ while (b !== 0) {
+ const bTemp = b;
+ b = a % b;
+ a = bTemp;
+ }
+
+ return a;
+}
+
+/** @returns the Least Common Multiplier (LCM) of the inputs */
+export function lcm(a, b) {
+ return a * b / gcd(a, b);
+}
+
+/** @returns the cross of an array with the intermediate result of cartesianProduct
+ *
+ * @param elements array of values to cross with the intermediate result of
+ * cartesianProduct
+ * @param intermediate arrays of values representing the partial result of
+ * cartesianProduct
+ */
+function cartesianProductImpl(
+elements,
+intermediate)
+{
+ const result = [];
+ elements.forEach((e) => {
+ if (intermediate.length > 0) {
+ intermediate.forEach((i) => {
+ result.push([...i, e]);
+ });
+ } else {
+ result.push([e]);
+ }
+ });
+ return result;
+}
+
+/** @returns the cartesian product (NxMx...) of a set of arrays
+ *
+ * This is implemented by calculating the cross of a single input against an
+ * intermediate result for each input to build up the final array of arrays.
+ *
+ * There are examples of doing this more succinctly using map & reduce online,
+ * but they are a bit more opaque to read.
+ *
+ * @param inputs arrays of numbers to calculate cartesian product over
+ */
+export function cartesianProduct(...inputs) {
+ let result = [];
+ inputs.forEach((i) => {
+ result = cartesianProductImpl(i, result);
+ });
+
+ return result;
+}
+
+/** @returns all of the permutations of an array
+ *
+ * Recursively calculates all of the permutations, does not cull duplicate
+ * entries.
+ *
+ * Only feasible for inputs of lengths 5 or so, since the number of permutations
+ * is (input.length)!, so will cause the stack to explode for longer inputs.
+ *
+ * This code could be made iterative using something like
+ * Steinhaus–Johnson–Trotter and additionally turned into a generator to reduce
+ * the stack size, but there is still a fundamental combinatorial explosion
+ * here that will affect runtime.
+ *
+ * @param input the array to get permutations of
+ */
+export function calculatePermutations(input) {
+ if (input.length === 0) {
+ return [];
+ }
+
+ if (input.length === 1) {
+ return [input];
+ }
+
+ if (input.length === 2) {
+ return [input, [input[1], input[0]]];
+ }
+
+ const result = [];
+ input.forEach((head, idx) => {
+ const tail = input.slice(0, idx).concat(input.slice(idx + 1));
+ const permutations = calculatePermutations(tail);
+ permutations.forEach((p) => {
+ result.push([head, ...p]);
+ });
+ });
+
+ return result;
+}
+
+/**
+ * Convert an Array of Arrays to linear array
+ *
+ * Caller is responsible to retaining the dimensions of the array for later
+ * unflattening
+ *
+ * @param m Matrix to convert
+ */
+export function flatten2DArray(m) {
+ const c = m.length;
+ const r = m[0].length;
+ assert(
+ m.every((c) => c.length === r),
+ `Unexpectedly received jagged array to flatten`
+ );
+ const result = Array(c * r);
+ for (let i = 0; i < c; i++) {
+ for (let j = 0; j < r; j++) {
+ result[j + i * r] = m[i][j];
+ }
+ }
+ return result;
+}
+
+/**
+ * Convert linear array to an Array of Arrays
+ * @param n an array to convert
+ * @param c number of elements in the array containing arrays
+ * @param r number of elements in the arrays that are contained
+ */
+export function unflatten2DArray(n, c, r) {
+ assert(
+ c > 0 && Number.isInteger(c) && r > 0 && Number.isInteger(r),
+ `columns (${c}) and rows (${r}) need to be positive integers`
+ );
+ assert(n.length === c * r, `m.length(${n.length}) should equal c * r (${c * r})`);
+ const result = [...Array(c)].map((_) => [...Array(r)]);
+ for (let i = 0; i < c; i++) {
+ for (let j = 0; j < r; j++) {
+ result[i][j] = n[j + i * r];
+ }
+ }
+ return result;
+}
+
+/**
+ * Performs a .map over a matrix and return the result
+ * The shape of the input and output matrices will be the same
+ *
+ * @param m input matrix of type T
+ * @param op operation that converts an element of type T to one of type S
+ * @returns a matrix with elements of type S that are calculated by applying op element by element
+ */
+export function map2DArray(m, op) {
+ const c = m.length;
+ const r = m[0].length;
+ assert(
+ m.every((c) => c.length === r),
+ `Unexpectedly received jagged array to map`
+ );
+ const result = [...Array(c)].map((_) => [...Array(r)]);
+ for (let i = 0; i < c; i++) {
+ for (let j = 0; j < r; j++) {
+ result[i][j] = op(m[i][j]);
+ }
+ }
+ return result;
+}
+
+/**
+ * Performs a .every over a matrix and return the result
+ *
+ * @param m input matrix of type T
+ * @param op operation that performs a test on an element
+ * @returns a boolean indicating if the test passed for every element
+ */
+export function every2DArray(m, op) {
+ const r = m[0].length;
+ assert(
+ m.every((c) => c.length === r),
+ `Unexpectedly received jagged array to map`
+ );
+ return m.every((col) => col.every((el) => op(el)));
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/memory.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/memory.js
new file mode 100644
index 0000000000..e48ab32fc9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/memory.js
@@ -0,0 +1,25 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Helper to exhaust VRAM until there is less than 64 MB of capacity. Returns
+ * an opaque closure which can be called to free the allocated resources later.
+ */export async function exhaustVramUntilUnder64MB(device) {const allocateUntilOom = async (device, size) => {
+ const buffers = [];
+ for (;;) {
+ device.pushErrorScope('out-of-memory');
+ const buffer = device.createBuffer({ size, usage: GPUBufferUsage.STORAGE });
+ if (await device.popErrorScope()) {
+ return buffers;
+ }
+ buffers.push(buffer);
+ }
+ };
+
+ const kLargeChunkSize = 512 * 1024 * 1024;
+ const kSmallChunkSize = 64 * 1024 * 1024;
+ const buffers = await allocateUntilOom(device, kLargeChunkSize);
+ buffers.push(...(await allocateUntilOom(device, kSmallChunkSize)));
+ return () => {
+ buffers.forEach((buffer) => buffer.destroy());
+ };
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/pretty_diff_tables.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/pretty_diff_tables.js
new file mode 100644
index 0000000000..3803540fe9
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/pretty_diff_tables.js
@@ -0,0 +1,51 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { range } from '../../common/util/util.js'; /**
+ * 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.
+ *
+ * The first argument is the printing options:
+ * - fillToWidth: Keep printing columns (as long as there is data) until this width is passed.
+ * If there is more data, "..." is appended.
+ * - numberToString: if a cell value is a number, this is used to stringify it.
+ *
+ * Each remaining argument provides one row for the table.
+ */
+export function generatePrettyTable(
+{ fillToWidth, numberToString },
+rows)
+{
+ const rowStrings = range(rows.length, () => '');
+ let totalTableWidth = 0;
+ const iters = rows.map((row) => row[Symbol.iterator]());
+
+ // Loop over columns
+ 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 (cellsForColumn.every((cell) => cell === undefined)) break;
+
+ // Maximum width of any cell in this column, plus one for space between columns
+ // (also inserts a space at the left of the first column).
+ const colWidth = Math.max(...cellsForColumn.map((c) => c === undefined ? 0 : c.length)) + 1;
+ for (let row = 0; row < rowStrings.length; ++row) {
+ const cell = cellsForColumn[row];
+ if (cell !== undefined) {
+ rowStrings[row] += cell.padStart(colWidth);
+ }
+ }
+
+ totalTableWidth += colWidth;
+ if (totalTableWidth >= fillToWidth) {
+ for (let row = 0; row < rowStrings.length; ++row) {
+ if (cellsForColumn[row] !== undefined) {
+ rowStrings[row] += ' ...';
+ }
+ }
+ break;
+ }
+ }
+ return rowStrings.join('\n');
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/prng.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/prng.js
new file mode 100644
index 0000000000..9e456d734e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/prng.js
@@ -0,0 +1,125 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../common/util/util.js';import { kValue } from './constants.js';
+
+/**
+ * Seed-able deterministic pseudo random generator for the WebGPU CTS
+ *
+ * This generator requires setting a seed value and the sequence of values
+ * generated is deterministic based on the seed.
+ *
+ * This generator is intended to be a replacement for Math.random().
+ *
+ * This generator is not cryptographically secure, though nothing in the CTS
+ * should be needing cryptographic security.
+ *
+ * The current implementation is based on TinyMT
+ * (https://github.com/MersenneTwister-Lab/TinyMT), which is a version of
+ * Mersenne Twister that has reduced the internal state size at the cost of
+ * shortening the period length of the generated sequence. The period is still
+ * 2^127 - 1 entries long, so should be sufficient for use in the CTS, but it is
+ * less costly to create multiple instances of the class.
+ */
+export class PRNG {
+ // Storing variables for temper() as members, so they don't need to be
+ // reallocated per call to temper()
+
+
+ // Storing variables for next() as members, so they don't need to be
+ // reallocated per call to next()
+
+
+ // Generator internal state
+
+
+ // Default tuning parameters for TinyMT.
+ // These are tested to not generate an all zero initial state.
+ static kMat1 = 0x8f7011ee;
+ static kMat2 = 0xfc78ff1f;
+ static kTMat = 0x3793fdff;
+
+ // TinyMT algorithm internal magic numbers
+ static kMask = 0x7fffffff;
+ static kMinLoop = 8;
+ static kPreLoop = 8;
+ static kSH0 = 1;
+ static kSH1 = 10;
+ static kSH8 = 8;
+
+ // u32.max + 1, used to scale the u32 value from temper() to [0, 1).
+ static kRandomDivisor = 4294967296.0;
+
+ /**
+ * constructor
+ *
+ * @param seed value used to initialize random number sequence. Results are
+ * guaranteed to be deterministic based on this.
+ * This value must be in the range of unsigned 32-bit integers.
+ * Non-integers will be rounded.
+ */
+ constructor(seed) {
+ assert(seed >= 0 && seed <= kValue.u32.max, 'seed to PRNG needs to a u32');
+
+ this.t_vars = new Uint32Array(2);
+ this.n_vars = new Uint32Array(2);
+
+ this.state = new Uint32Array([Math.round(seed), PRNG.kMat1, PRNG.kMat2, PRNG.kTMat]);
+ for (let i = 1; i < PRNG.kMinLoop; i++) {
+ this.state[i & 3] ^=
+ i + Math.imul(1812433253, this.state[i - 1 & 3] ^ this.state[i - 1 & 3] >>> 30);
+ }
+
+ // Check that the initial state isn't all 0s, since the algorithm assumes
+ // that this never occurs
+ assert(
+ (this.state[0] & PRNG.kMask) !== 0 ||
+ this.state[1] !== 0 ||
+ this.state[2] !== 0 ||
+ this.state[2] !== 0,
+ 'Initialization of PRNG unexpectedly generated all 0s initial state, this means the tuning parameters are bad'
+ );
+
+ for (let i = 0; i < PRNG.kPreLoop; i++) {
+ this.next();
+ }
+ }
+
+ /** Advances the internal state to the next values */
+ next() {
+ this.n_vars[0] = this.state[0] & PRNG.kMask ^ this.state[1] ^ this.state[2];
+ this.n_vars[1] = this.state[3];
+ this.n_vars[0] ^= this.n_vars[0] << PRNG.kSH0;
+ this.n_vars[1] ^= this.n_vars[1] >>> PRNG.kSH0 ^ this.n_vars[0];
+ this.state[0] = this.state[1];
+ this.state[1] = this.state[2];
+ this.state[2] = this.n_vars[0] ^ this.n_vars[1] << PRNG.kSH1;
+ this.state[3] = this.n_vars[1];
+ if ((this.n_vars[1] & 1) !== 0) {
+ this.state[1] ^= PRNG.kMat1;
+ this.state[2] ^= PRNG.kMat2;
+ }
+ }
+
+ /** @returns a 32-bit unsigned integer based on the current state */
+ temper() {
+ this.t_vars[0] = this.state[3];
+ this.t_vars[1] = this.state[0] + (this.state[2] >>> PRNG.kSH8);
+ this.t_vars[0] ^= this.t_vars[1];
+ if ((this.t_vars[1] & 1) !== 0) {
+ this.t_vars[0] ^= PRNG.kTMat;
+ }
+ return this.t_vars[0];
+ }
+
+ /** @returns a value on the range of [0, 1) and advances the state */
+ random() {
+ this.next();
+ return this.temper() / PRNG.kRandomDivisor;
+ }
+
+ /** @returns a 32-bit unsigned integer value and advances the state */
+ randomU32() {
+ this.next();
+ return this.temper();
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/reinterpret.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/reinterpret.js
new file mode 100644
index 0000000000..69eebb1e8c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/reinterpret.js
@@ -0,0 +1,118 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { Float16Array } from '../../external/petamoriken/float16/float16.js'; /**
+ * Once-allocated ArrayBuffer/views to avoid overhead of allocation when converting between numeric formats
+ *
+ * workingData* is shared between multiple functions in this file, so to avoid re-entrancy problems, make sure in
+ * functions that use it that they don't call themselves or other functions that use workingData*.
+ */
+const workingData = new ArrayBuffer(8);
+const workingDataU32 = new Uint32Array(workingData);
+const workingDataU16 = new Uint16Array(workingData);
+const workingDataF32 = new Float32Array(workingData);
+const workingDataF16 = new Float16Array(workingData);
+const workingDataI32 = new Int32Array(workingData);
+const workingDataF64 = new Float64Array(workingData);
+const workingDataU64 = new BigUint64Array(workingData);
+
+/**
+ * @returns a 64-bit float value via interpreting the input as the bit
+ * representation as a 64-bit integer
+ */
+export function reinterpretU64AsF64(input) {
+ workingDataU64[0] = input;
+ return workingDataF64[0];
+}
+
+/**
+ * @returns the 64-bit integer bit representation of 64-bit float value
+ */
+export function reinterpretF64AsU64(input) {
+ workingDataF64[0] = input;
+ return workingDataU64[0];
+}
+
+// Encoding to u32s, instead of BigInt, for serialization
+export function reinterpretF64AsU32s(f64) {
+ workingDataF64[0] = f64;
+ return [workingDataU32[0], workingDataU32[1]];
+}
+
+// De-encoding from u32s, instead of BigInt, for serialization
+export function reinterpretU32sAsF64(u32s) {
+ workingDataU32[0] = u32s[0];
+ workingDataU32[1] = u32s[1];
+ return workingDataF64[0];
+}
+
+/**
+ * @returns a number representing the u32 interpretation
+ * of the bits of a number assumed to be an f32 value.
+ */
+export function reinterpretF32AsU32(f32) {
+ workingDataF32[0] = f32;
+ return workingDataU32[0];
+}
+
+/**
+ * @returns a number representing the i32 interpretation
+ * of the bits of a number assumed to be an f32 value.
+ */
+export function reinterpretF32AsI32(f32) {
+ workingDataF32[0] = f32;
+ return workingDataI32[0];
+}
+
+/**
+ * @returns a number representing the f32 interpretation
+ * of the bits of a number assumed to be an u32 value.
+ */
+export function reinterpretU32AsF32(u32) {
+ workingDataU32[0] = u32;
+ return workingDataF32[0];
+}
+
+/**
+ * @returns a number representing the i32 interpretation
+ * of the bits of a number assumed to be an u32 value.
+ */
+export function reinterpretU32AsI32(u32) {
+ workingDataU32[0] = u32;
+ return workingDataI32[0];
+}
+
+/**
+ * @returns a number representing the u32 interpretation
+ * of the bits of a number assumed to be an i32 value.
+ */
+export function reinterpretI32AsU32(i32) {
+ workingDataI32[0] = i32;
+ return workingDataU32[0];
+}
+
+/**
+ * @returns a number representing the f32 interpretation
+ * of the bits of a number assumed to be an i32 value.
+ */
+export function reinterpretI32AsF32(i32) {
+ workingDataI32[0] = i32;
+ return workingDataF32[0];
+}
+
+/**
+ * @returns a number representing the u16 interpretation
+ * of the bits of a number assumed to be an f16 value.
+ */
+export function reinterpretF16AsU16(f16) {
+ workingDataF16[0] = f16;
+ return workingDataU16[0];
+}
+
+/**
+ * @returns a number representing the f16 interpretation
+ * of the bits of a number assumed to be an u16 value.
+ */
+export function reinterpretU16AsF16(u16) {
+ workingDataU16[0] = u16;
+ return workingDataF16[0];
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/shader.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/shader.js
new file mode 100644
index 0000000000..51029e0c82
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/shader.js
@@ -0,0 +1,196 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { unreachable } from '../../common/util/util.js';export const kDefaultVertexShaderCode = `
+@vertex fn main() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+`;
+
+export const kDefaultFragmentShaderCode = `
+@fragment fn main() -> @location(0) vec4<f32> {
+ return vec4<f32>(1.0, 1.0, 1.0, 1.0);
+}`;
+
+const kPlainTypeInfo = {
+ i32: {
+ suffix: '',
+ fractionDigits: 0
+ },
+ u32: {
+ suffix: 'u',
+ fractionDigits: 0
+ },
+ f32: {
+ suffix: '',
+ fractionDigits: 4
+ }
+};
+
+/**
+ *
+ * @param sampleType sampleType of texture format
+ * @returns plain type compatible of the sampleType
+ */
+export function getPlainTypeInfo(sampleType) {
+ switch (sampleType) {
+ case 'sint':
+ return 'i32';
+ case 'uint':
+ return 'u32';
+ case 'float':
+ case 'unfilterable-float':
+ case 'depth':
+ return 'f32';
+ default:
+ unreachable();
+ }
+}
+
+/**
+ * Build a fragment shader based on output value and types
+ * e.g. write to color target 0 a `vec4<f32>(1.0, 0.0, 1.0, 1.0)` and color target 2 a `vec2<u32>(1, 2)`
+ * ```
+ * outputs: [
+ * {
+ * values: [1, 0, 1, 1],,
+ * plainType: 'f32',
+ * componentCount: 4,
+ * },
+ * null,
+ * {
+ * values: [1, 2],
+ * plainType: 'u32',
+ * componentCount: 2,
+ * },
+ * ]
+ * ```
+ *
+ * return:
+ * ```
+ * struct Outputs {
+ * @location(0) o1 : vec4<f32>,
+ * @location(2) o3 : vec2<u32>,
+ * }
+ * @fragment fn main() -> Outputs {
+ * return Outputs(vec4<f32>(1.0, 0.0, 1.0, 1.0), vec4<u32>(1, 2));
+ * }
+ * ```
+ *
+ * If fragDepth is given there will be an extra @builtin(frag_depth) output with the specified value assigned.
+ *
+ * @param outputs the shader outputs for each location attribute
+ * @param fragDepth the shader outputs frag_depth value (optional)
+ * @returns the fragment shader string
+ */
+export function getFragmentShaderCodeWithOutput(
+outputs,
+
+
+
+
+fragDepth = null)
+{
+ if (outputs.length === 0) {
+ if (fragDepth) {
+ return `
+ @fragment fn main() -> @builtin(frag_depth) f32 {
+ return ${fragDepth.value.toFixed(kPlainTypeInfo['f32'].fractionDigits)};
+ }`;
+ }
+ return `
+ @fragment fn main() {
+ }`;
+ }
+
+ const resultStrings = [];
+ let outputStructString = '';
+
+ if (fragDepth) {
+ resultStrings.push(`${fragDepth.value.toFixed(kPlainTypeInfo['f32'].fractionDigits)}`);
+ outputStructString += `@builtin(frag_depth) depth_out: f32,\n`;
+ }
+
+ for (let i = 0; i < outputs.length; i++) {
+ const o = outputs[i];
+ if (o === null) {
+ continue;
+ }
+
+ const plainType = o.plainType;
+ const { suffix, fractionDigits } = kPlainTypeInfo[plainType];
+
+ let outputType;
+ const v = o.values.map((n) => n.toFixed(fractionDigits));
+ switch (o.componentCount) {
+ case 1:
+ outputType = plainType;
+ resultStrings.push(`${v[0]}${suffix}`);
+ break;
+ case 2:
+ outputType = `vec2<${plainType}>`;
+ resultStrings.push(`${outputType}(${v[0]}${suffix}, ${v[1]}${suffix})`);
+ break;
+ case 3:
+ outputType = `vec3<${plainType}>`;
+ resultStrings.push(`${outputType}(${v[0]}${suffix}, ${v[1]}${suffix}, ${v[2]}${suffix})`);
+ break;
+ case 4:
+ outputType = `vec4<${plainType}>`;
+ resultStrings.push(
+ `${outputType}(${v[0]}${suffix}, ${v[1]}${suffix}, ${v[2]}${suffix}, ${v[3]}${suffix})`
+ );
+ break;
+ default:
+ unreachable();
+ }
+
+ outputStructString += `@location(${i}) o${i} : ${outputType},\n`;
+ }
+
+ return `
+ struct Outputs {
+ ${outputStructString}
+ }
+
+ @fragment fn main() -> Outputs {
+ return Outputs(${resultStrings.join(',')});
+ }`;
+}
+
+
+
+/**
+ * Return a foo shader of the given stage with the given entry point
+ * @param shaderStage
+ * @param entryPoint
+ * @returns the shader string
+ */
+export function getShaderWithEntryPoint(shaderStage, entryPoint) {
+ let code;
+ switch (shaderStage) {
+ case 'compute':{
+ code = `@compute @workgroup_size(1) fn ${entryPoint}() {}`;
+ break;
+ }
+ case 'vertex':{
+ code = `
+ @vertex fn ${entryPoint}() -> @builtin(position) vec4<f32> {
+ return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ }`;
+ break;
+ }
+ case 'fragment':{
+ code = `
+ @fragment fn ${entryPoint}() -> @location(0) vec4<f32> {
+ return vec4<f32>(0.0, 1.0, 0.0, 1.0);
+ }`;
+ break;
+ }
+ case 'empty':
+ default:{
+ code = '';
+ break;
+ }
+ }
+ return code;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture.js
new file mode 100644
index 0000000000..c2c980c975
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture.js
@@ -0,0 +1,81 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../common/util/util.js';import { getTextureCopyLayout } from './texture/layout.js';
+
+import { reifyExtent3D } from './unions.js';
+
+/**
+ * Creates a mipmapped texture where each mipmap level's (`i`) content is
+ * from `texelViews[i]`.
+ */
+export function createTextureFromTexelViews(
+device,
+texelViews,
+desc)
+{
+ // All texel views must be the same format for mipmaps.
+ assert(texelViews.length > 0 && texelViews.every((e) => e.format === texelViews[0].format));
+ const format = texelViews[0].format;
+ const { width, height, depthOrArrayLayers } = reifyExtent3D(desc.size);
+
+ // Create the texture and then initialize each mipmap level separately.
+ const texture = device.createTexture({
+ ...desc,
+ format: texelViews[0].format,
+ usage: desc.usage | GPUTextureUsage.COPY_DST,
+ mipLevelCount: texelViews.length
+ });
+
+ // Copy the texel view into each mip level layer.
+ const commandEncoder = device.createCommandEncoder();
+ const stagingBuffers = [];
+ for (let mipLevel = 0; mipLevel < texelViews.length; mipLevel++) {
+ const {
+ bytesPerRow,
+ mipSize: [mipWidth, mipHeight, mipDepthOrArray]
+ } = getTextureCopyLayout(format, desc.dimension ?? '2d', [width, height, depthOrArrayLayers], {
+ mipLevel
+ });
+
+ // Create a staging buffer to upload the texture mip level contents.
+ const stagingBuffer = device.createBuffer({
+ mappedAtCreation: true,
+ size: bytesPerRow * mipHeight * mipDepthOrArray,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ stagingBuffers.push(stagingBuffer);
+
+ // Write the texels into the staging buffer.
+ texelViews[mipLevel].writeTextureData(new Uint8Array(stagingBuffer.getMappedRange()), {
+ bytesPerRow,
+ rowsPerImage: mipHeight,
+ subrectOrigin: [0, 0, 0],
+ subrectSize: [mipWidth, mipHeight, mipDepthOrArray]
+ });
+ stagingBuffer.unmap();
+
+ // Copy from the staging buffer into the texture.
+ commandEncoder.copyBufferToTexture(
+ { buffer: stagingBuffer, bytesPerRow },
+ { texture, mipLevel },
+ [mipWidth, mipHeight, mipDepthOrArray]
+ );
+ }
+ device.queue.submit([commandEncoder.finish()]);
+
+ // Cleanup the staging buffers.
+ stagingBuffers.forEach((value) => value.destroy());
+
+ return texture;
+}
+
+/**
+ * Creates a 1 mip level texture with the contents of a TexelView.
+ */
+export function createTextureFromTexelView(
+device,
+texelView,
+desc)
+{
+ return createTextureFromTexelViews(device, [texelView], desc);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/base.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/base.js
new file mode 100644
index 0000000000..349a08a486
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/base.js
@@ -0,0 +1,243 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../common/util/util.js';import { kTextureFormatInfo } from '../../format_info.js';import { align } from '../../util/math.js';
+import { reifyExtent3D } from '../../util/unions.js';
+
+/**
+ * Compute the maximum mip level count allowed for a given texture size and texture dimension.
+ */
+export function maxMipLevelCount({
+ size,
+ dimension = '2d'
+
+
+
+}) {
+ const sizeDict = reifyExtent3D(size);
+
+ let maxMippedDimension = 0;
+ switch (dimension) {
+ case '1d':
+ maxMippedDimension = 1; // No mipmaps allowed.
+ break;
+ case '2d':
+ maxMippedDimension = Math.max(sizeDict.width, sizeDict.height);
+ break;
+ case '3d':
+ maxMippedDimension = Math.max(sizeDict.width, sizeDict.height, sizeDict.depthOrArrayLayers);
+ break;
+ }
+
+ return Math.floor(Math.log2(maxMippedDimension)) + 1;
+}
+
+/**
+ * Compute the "physical size" of a mip level: the size of the level, rounded up to a
+ * multiple of the texel block size.
+ */
+export function physicalMipSize(
+baseSize,
+format,
+dimension,
+level)
+{
+ switch (dimension) {
+ case '1d':
+ assert(level === 0, '1d textures cannot be mipmapped');
+ assert(baseSize.height === 1 && baseSize.depthOrArrayLayers === 1, '1d texture not Wx1x1');
+ return { width: baseSize.width, height: 1, depthOrArrayLayers: 1 };
+
+ case '2d':{
+ assert(
+ Math.max(baseSize.width, baseSize.height) >> level > 0,
+ () => `level (${level}) too large for base size (${baseSize.width}x${baseSize.height})`
+ );
+
+ const virtualWidthAtLevel = Math.max(baseSize.width >> level, 1);
+ const virtualHeightAtLevel = Math.max(baseSize.height >> level, 1);
+ const physicalWidthAtLevel = align(
+ virtualWidthAtLevel,
+ kTextureFormatInfo[format].blockWidth
+ );
+ const physicalHeightAtLevel = align(
+ virtualHeightAtLevel,
+ kTextureFormatInfo[format].blockHeight
+ );
+ return {
+ width: physicalWidthAtLevel,
+ height: physicalHeightAtLevel,
+ depthOrArrayLayers: baseSize.depthOrArrayLayers
+ };
+ }
+
+ case '3d':{
+ assert(
+ Math.max(baseSize.width, baseSize.height, baseSize.depthOrArrayLayers) >> level > 0,
+ () =>
+ `level (${level}) too large for base size (${baseSize.width}x${baseSize.height}x${baseSize.depthOrArrayLayers})`
+ );
+ assert(
+ kTextureFormatInfo[format].blockWidth === 1 && kTextureFormatInfo[format].blockHeight === 1,
+ 'not implemented for 3d block formats'
+ );
+ return {
+ width: Math.max(baseSize.width >> level, 1),
+ height: Math.max(baseSize.height >> level, 1),
+ depthOrArrayLayers: Math.max(baseSize.depthOrArrayLayers >> level, 1)
+ };
+ }
+ }
+}
+
+/**
+ * Compute the "physical size" of a mip level: the size of the level, rounded up to a
+ * multiple of the texel block size.
+ */
+export function physicalMipSizeFromTexture(
+texture,
+mipLevel)
+{
+ const size = physicalMipSize(texture, texture.format, texture.dimension, mipLevel);
+ return [size.width, size.height, size.depthOrArrayLayers];
+}
+
+/**
+ * Compute the "virtual size" of a mip level of a texture (not accounting for texel block rounding).
+ *
+ * MAINTENANCE_TODO: Change input/output to Required<GPUExtent3DDict> for consistency.
+ */
+export function virtualMipSize(
+dimension,
+size,
+mipLevel)
+{
+ const shiftMinOne = (n) => Math.max(1, n >> mipLevel);
+ switch (dimension) {
+ case '1d':
+ assert(size[2] === 1);
+ return [shiftMinOne(size[0]), size[1], size[2]];
+ case '2d':
+ return [shiftMinOne(size[0]), shiftMinOne(size[1]), size[2]];
+ case '3d':
+ return [shiftMinOne(size[0]), shiftMinOne(size[1]), shiftMinOne(size[2])];
+ default:
+ unreachable();
+ }
+}
+
+/**
+ * Get texture dimension from view dimension in order to create an compatible texture for a given
+ * view dimension.
+ */
+export function getTextureDimensionFromView(viewDimension) {
+ switch (viewDimension) {
+ case '1d':
+ return '1d';
+ case '2d':
+ case '2d-array':
+ case 'cube':
+ case 'cube-array':
+ return '2d';
+ case '3d':
+ return '3d';
+ default:
+ unreachable();
+ }
+}
+
+/** Returns the possible valid view dimensions for a given texture dimension. */
+export function viewDimensionsForTextureDimension(textureDimension) {
+ switch (textureDimension) {
+ case '1d':
+ return ['1d'];
+ case '2d':
+ return ['2d', '2d-array', 'cube', 'cube-array'];
+ case '3d':
+ return ['3d'];
+ }
+}
+
+/** Returns the default view dimension for a given texture descriptor. */
+export function defaultViewDimensionsForTexture(textureDescriptor) {
+ switch (textureDescriptor.dimension) {
+ case '1d':
+ return '1d';
+ case '2d':{
+ const sizeDict = reifyExtent3D(textureDescriptor.size);
+ return sizeDict.depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ }
+ case '3d':
+ return '3d';
+ default:
+ unreachable();
+ }
+}
+
+/** 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.
+ */
+export function reifyTextureDescriptor(
+desc)
+{
+ return { dimension: '2d', mipLevelCount: 1, sampleCount: 1, ...desc };
+}
+
+/** Reifies the optional fields of `GPUTextureViewDescriptor` (given a `GPUTextureDescriptor`). */
+export function reifyTextureViewDescriptor(
+textureDescriptor,
+view)
+{
+ const texture = reifyTextureDescriptor(textureDescriptor);
+
+ // IDL defaulting
+
+ const baseMipLevel = view.baseMipLevel ?? 0;
+ const baseArrayLayer = view.baseArrayLayer ?? 0;
+ const aspect = view.aspect ?? 'all';
+
+ // Spec defaulting
+
+ const format = view.format ?? texture.format;
+ const mipLevelCount = view.mipLevelCount ?? texture.mipLevelCount - baseMipLevel;
+ const dimension = view.dimension ?? defaultViewDimensionsForTexture(texture);
+
+ let arrayLayerCount = view.arrayLayerCount;
+ if (arrayLayerCount === undefined) {
+ if (dimension === '2d-array' || dimension === 'cube-array') {
+ arrayLayerCount = reifyExtent3D(texture.size).depthOrArrayLayers - baseArrayLayer;
+ } else if (dimension === 'cube') {
+ arrayLayerCount = 6;
+ } else {
+ arrayLayerCount = 1;
+ }
+ }
+
+ return {
+ format,
+ dimension,
+ aspect,
+ baseMipLevel,
+ mipLevelCount,
+ baseArrayLayer,
+ arrayLayerCount
+ };
+}
+
+/**
+ * Get generator of all the coordinates in a subrect.
+ * @param subrectOrigin - Subrect origin
+ * @param subrectSize - Subrect size
+ */
+export function* fullSubrectCoordinates(
+subrectOrigin,
+subrectSize)
+{
+ for (let z = subrectOrigin.z; z < subrectOrigin.z + subrectSize.depthOrArrayLayers; ++z) {
+ for (let y = subrectOrigin.y; y < subrectOrigin.y + subrectSize.height; ++y) {
+ for (let x = subrectOrigin.x; x < subrectOrigin.x + subrectSize.width; ++x) {
+ yield { x, y, z };
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/data_generation.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/data_generation.js
new file mode 100644
index 0000000000..5ab5338014
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/data_generation.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * A helper class that generates ranges of dummy data for buffer or texture operations
+ * efficiently. Tries to minimize allocations and data updates.
+ */export class DataArrayGenerator {dataBuffer = new Uint8Array(256);
+
+ lastOffset = 0;
+ lastStart = 0;
+ lastByteSize = 0;
+
+ /** Find the nearest power of two greater than or equal to the input value. */
+ nextPowerOfTwo(value) {
+ return 1 << 32 - Math.clz32(value - 1);
+ }
+
+ generateData(byteSize, start = 0, offset = 0) {
+ const prevSize = this.dataBuffer.length;
+
+ if (prevSize < byteSize) {
+ // If the requested data is larger than the allocated buffer, reallocate it to a buffer large
+ // enough to handle the new request.
+ const newData = new Uint8Array(this.nextPowerOfTwo(byteSize));
+
+ if (this.lastOffset === offset && this.lastStart === start && this.lastByteSize) {
+ // Do a fast copy of any previous data that was generated.
+ newData.set(this.dataBuffer);
+ }
+
+ this.dataBuffer = newData;
+ } else if (this.lastOffset < offset) {
+ // Ensure all values up to the offset are zeroed out.
+ this.dataBuffer.fill(0, this.lastOffset, offset);
+ }
+
+ // If the offset or start values have changed, the whole data range needs to be regenerated.
+ if (this.lastOffset !== offset || this.lastStart !== start) {
+ this.lastByteSize = 0;
+ }
+
+ // Generate any new values that are required
+ if (this.lastByteSize < byteSize) {
+ for (let i = this.lastByteSize; i < byteSize - offset; ++i) {
+ this.dataBuffer[i + offset] = (i ** 3 + i + start) % 251 + 1; // Ensure data is always non-zero
+ }
+
+ this.lastOffset = offset;
+ this.lastStart = start;
+ this.lastByteSize = byteSize;
+ }
+ }
+
+ /**
+ * Returns a new view into the generated data that's the correct length. Because this is a view
+ * previously returned views from the same generator will have their values overwritten as well.
+ * @param {number} byteSize - Number of bytes the returned view should contain.
+ * @param {number} [start] - The value of the first element generated in the view.
+ * @param {number} [offset] - Offset of the generated data within the view. Preceeding values will be 0.
+ * @returns {Uint8Array} A new Uint8Array view into the generated data.
+ */
+ generateView(byteSize, start = 0, offset = 0) {
+ this.generateData(byteSize, start, offset);
+
+ if (this.dataBuffer.length === byteSize) {
+ return this.dataBuffer;
+ }
+ return new Uint8Array(this.dataBuffer.buffer, 0, byteSize);
+ }
+
+ /**
+ * Returns a copy of the generated data. Note that this still changes the underlying buffer, so
+ * any previously generated views will still be overwritten, but the returned copy won't reflect
+ * future generate* calls.
+ * @param {number} byteSize - Number of bytes the returned array should contain.
+ * @param {number} [start] - The value of the first element generated in the view.
+ * @param {number} [offset] - Offset of the generated data within the view. Preceeding values will be 0.
+ * @returns {Uint8Array} A new Uint8Array copy of the generated data.
+ */
+ generateAndCopyView(byteSize, start = 0, offset = 0) {
+ this.generateData(byteSize, start, offset);
+ return this.dataBuffer.slice(0, byteSize);
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/layout.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/layout.js
new file mode 100644
index 0000000000..36603a1df4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/layout.js
@@ -0,0 +1,371 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, memcpy } from '../../../common/util/util.js';import { kTextureFormatInfo,
+resolvePerAspectFormat } from
+
+
+'../../format_info.js';
+import { align } from '../math.js';
+import { reifyExtent3D } from '../unions.js';
+
+import { physicalMipSize, virtualMipSize } from './base.js';
+
+/** The minimum `bytesPerRow` alignment, per spec. */
+export const kBytesPerRowAlignment = 256;
+/** The minimum buffer copy alignment, per spec. */
+export const kBufferCopyAlignment = 4;
+
+/**
+ * Overridable layout options for {@link getTextureCopyLayout}.
+ */
+
+
+
+
+
+
+
+const kDefaultLayoutOptions = {
+ mipLevel: 0,
+ bytesPerRow: undefined,
+ rowsPerImage: undefined,
+ aspect: 'all'
+};
+
+/** The info returned by {@link getTextureSubCopyLayout}. */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/** The info returned by {@link getTextureCopyLayout}. */
+
+
+
+
+/**
+ * Computes layout information for a copy of the whole subresource at `mipLevel` of a GPUTexture
+ * of size `baseSize` with the provided `format` and `dimension`.
+ *
+ * Computes default values for `bytesPerRow` and `rowsPerImage` if not specified.
+ *
+ * MAINTENANCE_TODO: Change input/output to Required<GPUExtent3DDict> for consistency.
+ */
+export function getTextureCopyLayout(
+format,
+dimension,
+baseSize,
+{ mipLevel, bytesPerRow, rowsPerImage, aspect } = kDefaultLayoutOptions)
+{
+ const mipSize = physicalMipSize(
+ { width: baseSize[0], height: baseSize[1], depthOrArrayLayers: baseSize[2] },
+ format,
+ dimension,
+ mipLevel
+ );
+
+ const layout = getTextureSubCopyLayout(format, mipSize, { bytesPerRow, rowsPerImage, aspect });
+ return { ...layout, mipSize: [mipSize.width, mipSize.height, mipSize.depthOrArrayLayers] };
+}
+
+/**
+ * Computes layout information for a copy of size `copySize` to/from a GPUTexture with the provided
+ * `format`.
+ *
+ * Computes default values for `bytesPerRow` and `rowsPerImage` if not specified.
+ */
+export function getTextureSubCopyLayout(
+format,
+copySize,
+{
+ bytesPerRow,
+ rowsPerImage,
+ aspect = 'all'
+
+
+
+
+} = {})
+{
+ format = resolvePerAspectFormat(format, aspect);
+ const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
+ assert(bytesPerBlock !== undefined);
+
+ const copySize_ = reifyExtent3D(copySize);
+ assert(
+ copySize_.width > 0 && copySize_.height > 0 && copySize_.depthOrArrayLayers > 0,
+ 'not implemented for empty copySize'
+ );
+ assert(
+ copySize_.width % blockWidth === 0 && copySize_.height % blockHeight === 0,
+ () =>
+ `copySize (${copySize_.width},${copySize_.height}) must be a multiple of the block size (${blockWidth},${blockHeight})`
+ );
+ const copySizeBlocks = {
+ width: copySize_.width / blockWidth,
+ height: copySize_.height / blockHeight,
+ depthOrArrayLayers: copySize_.depthOrArrayLayers
+ };
+
+ const minBytesPerRow = copySizeBlocks.width * bytesPerBlock;
+ const alignedMinBytesPerRow = align(minBytesPerRow, kBytesPerRowAlignment);
+ if (bytesPerRow !== undefined) {
+ assert(bytesPerRow >= alignedMinBytesPerRow);
+ assert(bytesPerRow % kBytesPerRowAlignment === 0);
+ } else {
+ bytesPerRow = alignedMinBytesPerRow;
+ }
+
+ if (rowsPerImage !== undefined) {
+ assert(rowsPerImage >= copySizeBlocks.height);
+ } else {
+ rowsPerImage = copySizeBlocks.height;
+ }
+
+ const bytesPerSlice = bytesPerRow * rowsPerImage;
+ const sliceSize =
+ bytesPerRow * (copySizeBlocks.height - 1) + bytesPerBlock * copySizeBlocks.width;
+ const byteLength = bytesPerSlice * (copySizeBlocks.depthOrArrayLayers - 1) + sliceSize;
+
+ return {
+ bytesPerBlock,
+ byteLength: align(byteLength, kBufferCopyAlignment),
+ minBytesPerRow,
+ bytesPerRow,
+ rowsPerImage
+ };
+}
+
+/**
+ * Fill an ArrayBuffer with the linear-memory representation of a solid-color
+ * texture where every texel has the byte value `texelValue`.
+ * Preserves the contents of `outputBuffer` which are in "padding" space between image rows.
+ *
+ * Effectively emulates a copyTextureToBuffer from a solid-color texture to a buffer.
+ */
+export function fillTextureDataWithTexelValue(
+texelValue,
+format,
+dimension,
+outputBuffer,
+size,
+options = kDefaultLayoutOptions)
+{
+ const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
+ // Block formats are not handled correctly below.
+ assert(blockWidth === 1);
+ assert(blockHeight === 1);
+
+ assert(bytesPerBlock === texelValue.byteLength, 'texelValue must be of size bytesPerBlock');
+
+ const { byteLength, rowsPerImage, bytesPerRow } = getTextureCopyLayout(
+ format,
+ dimension,
+ size,
+ options
+ );
+
+ assert(byteLength <= outputBuffer.byteLength);
+
+ const mipSize = virtualMipSize(dimension, size, options.mipLevel);
+
+ const outputTexelValueBytes = new Uint8Array(outputBuffer);
+ for (let slice = 0; slice < mipSize[2]; ++slice) {
+ for (let row = 0; row < mipSize[1]; row += blockHeight) {
+ for (let col = 0; col < mipSize[0]; col += blockWidth) {
+ const byteOffset =
+ slice * rowsPerImage * bytesPerRow + row * bytesPerRow + col * texelValue.byteLength;
+ memcpy({ src: texelValue }, { dst: outputTexelValueBytes, start: byteOffset });
+ }
+ }
+ }
+}
+
+/**
+ * Create a `COPY_SRC` GPUBuffer containing the linear-memory representation of a solid-color
+ * texture where every texel has the byte value `texelValue`.
+ */
+export function createTextureUploadBuffer(
+texelValue,
+device,
+format,
+dimension,
+size,
+options = kDefaultLayoutOptions)
+
+
+
+
+{
+ const { byteLength, bytesPerRow, rowsPerImage, bytesPerBlock } = getTextureCopyLayout(
+ format,
+ dimension,
+ size,
+ options
+ );
+
+ const buffer = device.createBuffer({
+ mappedAtCreation: true,
+ size: byteLength,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ const mapping = buffer.getMappedRange();
+
+ assert(texelValue.byteLength === bytesPerBlock);
+ fillTextureDataWithTexelValue(texelValue, format, dimension, mapping, size, options);
+ buffer.unmap();
+
+ return {
+ buffer,
+ bytesPerRow,
+ rowsPerImage
+ };
+}
+
+
+export const kImageCopyTypes = [
+'WriteTexture',
+'CopyB2T',
+'CopyT2B'];
+
+
+/**
+ * Computes `bytesInACompleteRow` (as defined by the WebGPU spec) for image copies (B2T/T2B/writeTexture).
+ */
+export function bytesInACompleteRow(copyWidth, format) {
+ const info = kTextureFormatInfo[format];
+ assert(copyWidth % info.blockWidth === 0);
+ return info.bytesPerBlock * copyWidth / info.blockWidth;
+}
+
+function validateBytesPerRow({
+ bytesPerRow,
+ bytesInLastRow,
+ sizeInBlocks
+
+
+
+
+}) {
+ // If specified, layout.bytesPerRow must be greater than or equal to bytesInLastRow.
+ if (bytesPerRow !== undefined && bytesPerRow < bytesInLastRow) {
+ return false;
+ }
+ // If heightInBlocks > 1, layout.bytesPerRow must be specified.
+ // If copyExtent.depthOrArrayLayers > 1, layout.bytesPerRow and layout.rowsPerImage must be specified.
+ if (
+ bytesPerRow === undefined && (
+ sizeInBlocks.height > 1 || sizeInBlocks.depthOrArrayLayers > 1))
+ {
+ return false;
+ }
+ return true;
+}
+
+function validateRowsPerImage({
+ rowsPerImage,
+ sizeInBlocks
+
+
+
+}) {
+ // If specified, layout.rowsPerImage must be greater than or equal to heightInBlocks.
+ if (rowsPerImage !== undefined && rowsPerImage < sizeInBlocks.height) {
+ return false;
+ }
+ // If copyExtent.depthOrArrayLayers > 1, layout.bytesPerRow and layout.rowsPerImage must be specified.
+ if (rowsPerImage === undefined && sizeInBlocks.depthOrArrayLayers > 1) {
+ return false;
+ }
+ return true;
+}
+
+
+
+
+
+
+
+
+/**
+ * Validate a copy and compute the number of bytes it needs. Throws if the copy is invalid.
+ */
+export function dataBytesForCopyOrFail(args) {
+ const { minDataSizeOrOverestimate, copyValid } = dataBytesForCopyOrOverestimate(args);
+ assert(copyValid, 'copy was invalid');
+ return minDataSizeOrOverestimate;
+}
+
+/**
+ * Validate a copy and compute the number of bytes it needs. If the copy is invalid, attempts to
+ * "conservatively guess" (overestimate) the number of bytes that could be needed for a copy, even
+ * if the copy parameters turn out to be invalid. This hopes to avoid "buffer too small" validation
+ * errors when attempting to test other validation errors.
+ */
+export function dataBytesForCopyOrOverestimate({
+ layout,
+ format,
+ copySize: copySize_,
+ method
+}) {
+ const copyExtent = reifyExtent3D(copySize_);
+
+ const info = kTextureFormatInfo[format];
+ assert(copyExtent.width % info.blockWidth === 0);
+ assert(copyExtent.height % info.blockHeight === 0);
+ const sizeInBlocks = {
+ width: copyExtent.width / info.blockWidth,
+ height: copyExtent.height / info.blockHeight,
+ depthOrArrayLayers: copyExtent.depthOrArrayLayers
+ };
+ const bytesInLastRow = sizeInBlocks.width * info.bytesPerBlock;
+
+ let valid = true;
+ const offset = layout.offset ?? 0;
+ if (method !== 'WriteTexture') {
+ if (offset % info.bytesPerBlock !== 0) valid = false;
+ if (layout.bytesPerRow && layout.bytesPerRow % 256 !== 0) valid = false;
+ }
+
+ let requiredBytesInCopy = 0;
+ {
+ let { bytesPerRow, rowsPerImage } = layout;
+
+ // If bytesPerRow or rowsPerImage is invalid, guess a value for the sake of various tests that
+ // don't actually care about the exact value.
+ // (In particular for validation tests that want to test invalid bytesPerRow or rowsPerImage but
+ // need to make sure the total buffer size is still big enough.)
+ if (!validateBytesPerRow({ bytesPerRow, bytesInLastRow, sizeInBlocks })) {
+ bytesPerRow = undefined;
+ valid = false;
+ }
+ if (!validateRowsPerImage({ rowsPerImage, sizeInBlocks })) {
+ rowsPerImage = undefined;
+ valid = false;
+ }
+ // Pick values for cases when (a) bpr/rpi was invalid or (b) they're validly undefined.
+ bytesPerRow ??= align(info.bytesPerBlock * sizeInBlocks.width, 256);
+ rowsPerImage ??= sizeInBlocks.height;
+
+ if (copyExtent.depthOrArrayLayers > 1) {
+ const bytesPerImage = bytesPerRow * rowsPerImage;
+ const bytesBeforeLastImage = bytesPerImage * (copyExtent.depthOrArrayLayers - 1);
+ requiredBytesInCopy += bytesBeforeLastImage;
+ }
+ if (copyExtent.depthOrArrayLayers > 0) {
+ if (sizeInBlocks.height > 1) requiredBytesInCopy += bytesPerRow * (sizeInBlocks.height - 1);
+ if (sizeInBlocks.height > 0) requiredBytesInCopy += bytesInLastRow;
+ }
+ }
+
+ return { minDataSizeOrOverestimate: offset + requiredBytesInCopy, copyValid: valid };
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/subresource.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/subresource.js
new file mode 100644
index 0000000000..c6f9563262
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/subresource.js
@@ -0,0 +1,68 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /** A range of indices expressed as `{ begin, count }`. */
+
+
+
+/* A range of indices, expressed as `{ begin, end }`. */
+
+
+
+
+
+function endOfRange(r) {
+ return 'count' in r ? r.begin + r.count : r.end;
+}
+
+function* rangeAsIterator(r) {
+ for (let i = r.begin; i < endOfRange(r); ++i) {
+ yield i;
+ }
+}
+
+/**
+ * Represents a range of subresources of a single-plane texture:
+ * a min/max mip level and min/max array layer.
+ */
+export class SubresourceRange {
+
+
+
+ constructor(subresources)
+
+
+ {
+ this.mipRange = {
+ begin: subresources.mipRange.begin,
+ end: endOfRange(subresources.mipRange)
+ };
+ this.layerRange = {
+ begin: subresources.layerRange.begin,
+ end: endOfRange(subresources.layerRange)
+ };
+ }
+
+ /**
+ * Iterates over the "rectangle" of `{ level, layer }` pairs represented by the range.
+ */
+ *each() {
+ for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) {
+ for (let layer = this.layerRange.begin; layer < this.layerRange.end; ++layer) {
+ yield { level, layer };
+ }
+ }
+ }
+
+ /**
+ * Iterates over the mip levels represented by the range, each level including an iterator
+ * over the array layers at that level.
+ */
+ *mipLevels() {
+ for (let level = this.mipRange.begin; level < this.mipRange.end; ++level) {
+ yield {
+ level,
+ layers: rangeAsIterator(this.layerRange)
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.js
new file mode 100644
index 0000000000..602d642724
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.js
@@ -0,0 +1,980 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../common/util/util.js';import {
+ assertInIntegerRange,
+ float32ToFloatBits,
+ float32ToFloat16Bits,
+ floatAsNormalizedInteger,
+ gammaCompress,
+ gammaDecompress,
+ normalizedIntegerAsFloat,
+ packRGB9E5UFloat,
+ floatBitsToNumber,
+ float16BitsToFloat32,
+ floatBitsToNormalULPFromZero,
+ kFloat32Format,
+ kFloat16Format,
+ kUFloat9e5Format,
+ numberToFloat32Bits,
+ float32BitsToNumber,
+ numberToFloatBits,
+ ufloatM9E5BitsToNumber } from
+'../conversion.js';
+import { clamp, signExtend } from '../math.js';
+
+/** A component of a texture format: R, G, B, A, Depth, or Stencil. */
+export let TexelComponent = /*#__PURE__*/function (TexelComponent) {TexelComponent["R"] = "R";TexelComponent["G"] = "G";TexelComponent["B"] = "B";TexelComponent["A"] = "A";TexelComponent["Depth"] = "Depth";TexelComponent["Stencil"] = "Stencil";return TexelComponent;}({});
+
+
+
+
+
+
+
+
+/** Arbitrary data, per component of a texel format. */
+
+
+/** How a component is encoded in its bit range of a texel format. */
+
+
+/**
+ * Maps component values to component values
+ * @param {PerTexelComponent<number>} components - The input components.
+ * @returns {PerTexelComponent<number>} The new output components.
+ */
+
+
+/**
+ * Packs component values as an ArrayBuffer
+ * @param {PerTexelComponent<number>} components - The input components.
+ * @returns {ArrayBuffer} The packed data.
+ */
+
+
+/** Unpacks component values from a Uint8Array */
+
+
+/**
+ * Create a PerTexelComponent object filled with the same value for all components.
+ * @param {TexelComponent[]} components - The component names.
+ * @param {T} value - The value to assign to each component.
+ * @returns {PerTexelComponent<T>}
+ */
+function makePerTexelComponent(components, value) {
+ const values = {};
+ for (const c of components) {
+ values[c] = value;
+ }
+ return values;
+}
+
+/**
+ * Create a function which applies clones a `PerTexelComponent<number>` and then applies the
+ * function `fn` to each component of `components`.
+ * @param {(value: number) => number} fn - The mapping function to apply to component values.
+ * @param {TexelComponent[]} components - The component names.
+ * @returns {ComponentMapFn} The map function which clones the input component values, and applies
+ * `fn` to each of component of `components`.
+ */
+function applyEach(fn, components) {
+ return (values) => {
+ values = Object.assign({}, values);
+ for (const c of components) {
+ assert(values[c] !== undefined);
+ values[c] = fn(values[c]);
+ }
+ return values;
+ };
+}
+
+/**
+ * A `ComponentMapFn` for encoding sRGB.
+ * @param {PerTexelComponent<number>} components - The input component values.
+ * @returns {TexelComponent<number>} Gamma-compressed copy of `components`.
+ */
+const encodeSRGB = (components) => {
+ assert(
+ components.R !== undefined && components.G !== undefined && components.B !== undefined,
+ 'sRGB requires all of R, G, and B components'
+ );
+ return applyEach(gammaCompress, kRGB)(components);
+};
+
+/**
+ * A `ComponentMapFn` for decoding sRGB.
+ * @param {PerTexelComponent<number>} components - The input component values.
+ * @returns {TexelComponent<number>} Gamma-decompressed copy of `components`.
+ */
+const decodeSRGB = (components) => {
+ components = Object.assign({}, components);
+ assert(
+ components.R !== undefined && components.G !== undefined && components.B !== undefined,
+ 'sRGB requires all of R, G, and B components'
+ );
+ return applyEach(gammaDecompress, kRGB)(components);
+};
+
+/**
+ * Makes a `ComponentMapFn` for clamping values to the specified range.
+ */
+export function makeClampToRange(format) {
+ const repr = kTexelRepresentationInfo[format];
+ assert(repr.numericRange !== null, 'Format has unknown numericRange');
+ return applyEach((x) => clamp(x, repr.numericRange), repr.componentOrder);
+}
+
+// MAINTENANCE_TODO: Look into exposing this map to the test fixture so that it can be GCed at the
+// end of each test group. That would allow for caching of larger buffers (though it's unclear how
+// ofter larger buffers are used by packComponents.)
+const smallComponentDataViews = new Map();
+function getComponentDataView(byteLength) {
+ if (byteLength > 32) {
+ const buffer = new ArrayBuffer(byteLength);
+ return new DataView(buffer);
+ }
+ let dataView = smallComponentDataViews.get(byteLength);
+ if (!dataView) {
+ const buffer = new ArrayBuffer(byteLength);
+ dataView = new DataView(buffer);
+ smallComponentDataViews.set(byteLength, dataView);
+ }
+ return dataView;
+}
+
+/**
+ * Helper function to pack components as an ArrayBuffer.
+ * @param {TexelComponent[]} componentOrder - The order of the component data.
+ * @param {PerTexelComponent<number>} components - The input component values.
+ * @param {number | PerTexelComponent<number>} bitLengths - The length in bits of each component.
+ * If a single number, all components are the same length, otherwise this is a dictionary of
+ * per-component bit lengths.
+ * @param {ComponentDataType | PerTexelComponent<ComponentDataType>} componentDataTypes -
+ * The type of the data in `components`. If a single value, all components have the same value.
+ * Otherwise, this is a dictionary of per-component data types.
+ * @returns {ArrayBuffer} The packed component data.
+ */
+function packComponents(
+componentOrder,
+components,
+bitLengths,
+componentDataTypes)
+{
+ let bitLengthMap;
+ let totalBitLength;
+ if (typeof bitLengths === 'number') {
+ bitLengthMap = makePerTexelComponent(componentOrder, bitLengths);
+ totalBitLength = bitLengths * componentOrder.length;
+ } else {
+ bitLengthMap = bitLengths;
+ totalBitLength = Object.entries(bitLengthMap).reduce((acc, [, value]) => {
+ assert(value !== undefined);
+ return acc + value;
+ }, 0);
+ }
+ assert(totalBitLength % 8 === 0);
+
+ const componentDataTypeMap =
+ typeof componentDataTypes === 'string' || componentDataTypes === null ?
+ makePerTexelComponent(componentOrder, componentDataTypes) :
+ componentDataTypes;
+
+ const dataView = getComponentDataView(totalBitLength / 8);
+ let bitOffset = 0;
+ for (const c of componentOrder) {
+ const value = components[c];
+ const type = componentDataTypeMap[c];
+ const bitLength = bitLengthMap[c];
+ assert(value !== undefined);
+ assert(type !== undefined);
+ assert(bitLength !== undefined);
+
+ const byteOffset = Math.floor(bitOffset / 8);
+ const byteLength = Math.ceil(bitLength / 8);
+ switch (type) {
+ case 'uint':
+ case 'unorm':
+ if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) {
+ switch (byteLength) {
+ case 1:
+ dataView.setUint8(byteOffset, value);
+ break;
+ case 2:
+ dataView.setUint16(byteOffset, value, true);
+ break;
+ case 4:
+ dataView.setUint32(byteOffset, value, true);
+ break;
+ default:
+ unreachable();
+ }
+ } else {
+ // Packed representations are all 32-bit and use Uint as the data type.
+ // ex.) rg10b11float, rgb10a2unorm
+ switch (dataView.byteLength) {
+ case 4:{
+ const currentValue = dataView.getUint32(0, true);
+
+ let mask = 0xffffffff;
+ const bitsToClearRight = bitOffset;
+ const bitsToClearLeft = 32 - (bitLength + bitOffset);
+
+ mask = mask >>> bitsToClearRight << bitsToClearRight;
+ mask = mask << bitsToClearLeft >>> bitsToClearLeft;
+
+ const newValue = currentValue & ~mask | value << bitOffset;
+
+ dataView.setUint32(0, newValue, true);
+ break;
+ }
+ default:
+ unreachable();
+ }
+ }
+ break;
+ case 'sint':
+ case 'snorm':
+ assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
+ switch (byteLength) {
+ case 1:
+ dataView.setInt8(byteOffset, value);
+ break;
+ case 2:
+ dataView.setInt16(byteOffset, value, true);
+ break;
+ case 4:
+ dataView.setInt32(byteOffset, value, true);
+ break;
+ default:
+ unreachable();
+ }
+ break;
+ case 'float':
+ assert(byteOffset === bitOffset / 8 && byteLength === bitLength / 8);
+ switch (byteLength) {
+ case 4:
+ dataView.setFloat32(byteOffset, value, true);
+ break;
+ default:
+ unreachable();
+ }
+ break;
+ case 'ufloat':
+ case null:
+ unreachable();
+ }
+
+ bitOffset += bitLength;
+ }
+
+ return dataView.buffer;
+}
+
+/**
+ * Unpack substrings of bits from a Uint8Array, e.g. [8,8,8,8] or [9,9,9,5].
+ */
+function unpackComponentsBits(
+componentOrder,
+byteView,
+bitLengths)
+{
+ const components = makePerTexelComponent(componentOrder, 0);
+
+ let bitLengthMap;
+ let totalBitLength;
+ if (typeof bitLengths === 'number') {
+ let index = 0;
+ // Optimized cases for when the bit lengths are all a well aligned value.
+ switch (bitLengths) {
+ case 8:
+ for (const c of componentOrder) {
+ components[c] = byteView[index++];
+ }
+ return components;
+ case 16:{
+ const shortView = new Uint16Array(byteView.buffer, byteView.byteOffset);
+ for (const c of componentOrder) {
+ components[c] = shortView[index++];
+ }
+ return components;
+ }
+ case 32:{
+ const longView = new Uint32Array(byteView.buffer, byteView.byteOffset);
+ for (const c of componentOrder) {
+ components[c] = longView[index++];
+ }
+ return components;
+ }
+ }
+
+ bitLengthMap = makePerTexelComponent(componentOrder, bitLengths);
+ totalBitLength = bitLengths * componentOrder.length;
+ } else {
+ bitLengthMap = bitLengths;
+ totalBitLength = Object.entries(bitLengthMap).reduce((acc, [, value]) => {
+ assert(value !== undefined);
+ return acc + value;
+ }, 0);
+ }
+
+ assert(totalBitLength % 8 === 0);
+
+ const dataView = new DataView(byteView.buffer, byteView.byteOffset, byteView.byteLength);
+ let bitOffset = 0;
+ for (const c of componentOrder) {
+ const bitLength = bitLengthMap[c];
+ assert(bitLength !== undefined);
+
+ let value;
+
+ const byteOffset = Math.floor(bitOffset / 8);
+ const byteLength = Math.ceil(bitLength / 8);
+ if (byteOffset === bitOffset / 8 && byteLength === bitLength / 8) {
+ switch (byteLength) {
+ case 1:
+ value = dataView.getUint8(byteOffset);
+ break;
+ case 2:
+ value = dataView.getUint16(byteOffset, true);
+ break;
+ case 4:
+ value = dataView.getUint32(byteOffset, true);
+ break;
+ default:
+ unreachable();
+ }
+ } else {
+ // Packed representations are all 32-bit and use Uint as the data type.
+ // ex.) rg10b11float, rgb10a2unorm
+ assert(dataView.byteLength === 4);
+ const word = dataView.getUint32(0, true);
+ value = word >>> bitOffset & (1 << bitLength) - 1;
+ }
+
+ bitOffset += bitLength;
+ components[c] = value;
+ }
+
+ return components;
+}
+
+/**
+ * Create an entry in `kTexelRepresentationInfo` for normalized integer texel data with constant
+ * bitlength.
+ * @param {TexelComponent[]} componentOrder - The order of the component data.
+ * @param {number} bitLength - The number of bits in each component.
+ * @param {{signed: boolean; sRGB: boolean}} opt - Boolean flags for `signed` and `sRGB`.
+ */
+function makeNormalizedInfo(
+componentOrder,
+bitLength,
+opt)
+{
+ const encodeNonSRGB = applyEach(
+ (n) => floatAsNormalizedInteger(n, bitLength, opt.signed),
+ componentOrder
+ );
+ const decodeNonSRGB = applyEach(
+ (n) => normalizedIntegerAsFloat(n, bitLength, opt.signed),
+ componentOrder
+ );
+
+ const numberToBitsNonSRGB = applyEach(
+ (n) => floatAsNormalizedInteger(n, bitLength, opt.signed),
+ componentOrder
+ );
+ let bitsToNumberNonSRGB;
+ if (opt.signed) {
+ bitsToNumberNonSRGB = applyEach(
+ (n) => normalizedIntegerAsFloat(signExtend(n, bitLength), bitLength, opt.signed),
+ componentOrder
+ );
+ } else {
+ bitsToNumberNonSRGB = applyEach(
+ (n) => normalizedIntegerAsFloat(n, bitLength, opt.signed),
+ componentOrder
+ );
+ }
+
+ let encode;
+ let decode;
+ let numberToBits;
+ let bitsToNumber;
+ if (opt.sRGB) {
+ encode = (components) => encodeNonSRGB(encodeSRGB(components));
+ decode = (components) => decodeSRGB(decodeNonSRGB(components));
+ numberToBits = (components) => numberToBitsNonSRGB(encodeSRGB(components));
+ bitsToNumber = (components) => decodeSRGB(bitsToNumberNonSRGB(components));
+ } else {
+ encode = encodeNonSRGB;
+ decode = decodeNonSRGB;
+ numberToBits = numberToBitsNonSRGB;
+ bitsToNumber = bitsToNumberNonSRGB;
+ }
+
+ let bitsToULPFromZero;
+ if (opt.signed) {
+ const maxValue = (1 << bitLength - 1) - 1; // e.g. 127 for snorm8
+ bitsToULPFromZero = applyEach(
+ (n) => Math.max(-maxValue, signExtend(n, bitLength)),
+ componentOrder
+ );
+ } else {
+ bitsToULPFromZero = (components) => components;
+ }
+
+ const dataType = opt.signed ? 'snorm' : 'unorm';
+ return {
+ componentOrder,
+ componentInfo: makePerTexelComponent(componentOrder, {
+ dataType,
+ bitLength
+ }),
+ encode,
+ decode,
+ pack: (components) =>
+ packComponents(componentOrder, components, bitLength, dataType),
+ unpackBits: (data) => unpackComponentsBits(componentOrder, data, bitLength),
+ numberToBits,
+ bitsToNumber,
+ bitsToULPFromZero,
+ numericRange: { min: opt.signed ? -1 : 0, max: 1 }
+ };
+}
+
+/**
+ * Create an entry in `kTexelRepresentationInfo` for integer texel data with constant bitlength.
+ * @param {TexelComponent[]} componentOrder - The order of the component data.
+ * @param {number} bitLength - The number of bits in each component.
+ * @param {{signed: boolean}} opt - Boolean flag for `signed`.
+ */
+function makeIntegerInfo(
+componentOrder,
+bitLength,
+opt)
+{
+ assert(bitLength <= 32);
+ const numericRange = opt.signed ?
+ { min: -(2 ** (bitLength - 1)), max: 2 ** (bitLength - 1) - 1 } :
+ { min: 0, max: 2 ** bitLength - 1 };
+ const maxUnsignedValue = 2 ** bitLength;
+ const encode = applyEach(
+ (n) => (assertInIntegerRange(n, bitLength, opt.signed), n),
+ componentOrder
+ );
+ const decode = applyEach(
+ (n) => (assertInIntegerRange(n, bitLength, opt.signed), n),
+ componentOrder
+ );
+ const bitsToNumber = applyEach((n) => {
+ const decodedN = opt.signed ? n > numericRange.max ? n - maxUnsignedValue : n : n;
+ assertInIntegerRange(decodedN, bitLength, opt.signed);
+ return decodedN;
+ }, componentOrder);
+
+ let bitsToULPFromZero;
+ if (opt.signed) {
+ bitsToULPFromZero = applyEach((n) => signExtend(n, bitLength), componentOrder);
+ } else {
+ bitsToULPFromZero = (components) => components;
+ }
+
+ const dataType = opt.signed ? 'sint' : 'uint';
+ const bitMask = (1 << bitLength) - 1;
+ return {
+ componentOrder,
+ componentInfo: makePerTexelComponent(componentOrder, {
+ dataType,
+ bitLength
+ }),
+ encode,
+ decode,
+ pack: (components) =>
+ packComponents(componentOrder, components, bitLength, dataType),
+ unpackBits: (data) => unpackComponentsBits(componentOrder, data, bitLength),
+ numberToBits: applyEach((v) => v & bitMask, componentOrder),
+ bitsToNumber,
+ bitsToULPFromZero,
+ numericRange
+ };
+}
+
+/**
+ * Create an entry in `kTexelRepresentationInfo` for floating point texel data with constant
+ * bitlength.
+ * @param {TexelComponent[]} componentOrder - The order of the component data.
+ * @param {number} bitLength - The number of bits in each component.
+ */
+function makeFloatInfo(
+componentOrder,
+bitLength,
+{ restrictedDepth = false } = {})
+{
+ let encode;
+ let numberToBits;
+ let bitsToNumber;
+ let bitsToULPFromZero;
+ switch (bitLength) {
+ case 32:
+ if (restrictedDepth) {
+ encode = applyEach((v) => {
+ assert(v >= 0.0 && v <= 1.0, 'depth out of range');
+ return new Float32Array([v])[0];
+ }, componentOrder);
+ } else {
+ encode = applyEach((v) => new Float32Array([v])[0], componentOrder);
+ }
+ numberToBits = applyEach(numberToFloat32Bits, componentOrder);
+ bitsToNumber = applyEach(float32BitsToNumber, componentOrder);
+ bitsToULPFromZero = applyEach(
+ (v) => floatBitsToNormalULPFromZero(v, kFloat32Format),
+ componentOrder
+ );
+ break;
+ case 16:
+ if (restrictedDepth) {
+ encode = applyEach((v) => {
+ assert(v >= 0.0 && v <= 1.0, 'depth out of range');
+ return float16BitsToFloat32(float32ToFloat16Bits(v));
+ }, componentOrder);
+ } else {
+ encode = applyEach((v) => float16BitsToFloat32(float32ToFloat16Bits(v)), componentOrder);
+ }
+ numberToBits = applyEach(float32ToFloat16Bits, componentOrder);
+ bitsToNumber = applyEach(float16BitsToFloat32, componentOrder);
+ bitsToULPFromZero = applyEach(
+ (v) => floatBitsToNormalULPFromZero(v, kFloat16Format),
+ componentOrder
+ );
+ break;
+ default:
+ unreachable();
+ }
+ const decode = applyEach(identity, componentOrder);
+
+ return {
+ componentOrder,
+ componentInfo: makePerTexelComponent(componentOrder, {
+ dataType: 'float',
+ bitLength
+ }),
+ encode,
+ decode,
+ pack: (components) => {
+ switch (bitLength) {
+ case 16:
+ components = applyEach(float32ToFloat16Bits, componentOrder)(components);
+ return packComponents(componentOrder, components, 16, 'uint');
+ case 32:
+ return packComponents(componentOrder, components, bitLength, 'float');
+ default:
+ unreachable();
+ }
+ },
+ unpackBits: (data) => unpackComponentsBits(componentOrder, data, bitLength),
+ numberToBits,
+ bitsToNumber,
+ bitsToULPFromZero,
+ numericRange: restrictedDepth ?
+ { min: 0, max: 1 } :
+ { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY }
+ };
+}
+
+const kR = [TexelComponent.R];
+const kRG = [TexelComponent.R, TexelComponent.G];
+const kRGB = [TexelComponent.R, TexelComponent.G, TexelComponent.B];
+const kRGBA = [TexelComponent.R, TexelComponent.G, TexelComponent.B, TexelComponent.A];
+const kBGRA = [TexelComponent.B, TexelComponent.G, TexelComponent.R, TexelComponent.A];
+
+const identity = (n) => n;
+
+const kFloat11Format = { signed: 0, exponentBits: 5, mantissaBits: 6, bias: 15 };
+const kFloat10Format = { signed: 0, exponentBits: 5, mantissaBits: 5, bias: 15 };
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+export const kTexelRepresentationInfo =
+
+{
+ ...{
+ 'r8unorm': makeNormalizedInfo(kR, 8, { signed: false, sRGB: false }),
+ 'r8snorm': makeNormalizedInfo(kR, 8, { signed: true, sRGB: false }),
+ 'r8uint': makeIntegerInfo(kR, 8, { signed: false }),
+ 'r8sint': makeIntegerInfo(kR, 8, { signed: true }),
+ 'r16uint': makeIntegerInfo(kR, 16, { signed: false }),
+ 'r16sint': makeIntegerInfo(kR, 16, { signed: true }),
+ 'r16float': makeFloatInfo(kR, 16),
+ 'rg8unorm': makeNormalizedInfo(kRG, 8, { signed: false, sRGB: false }),
+ 'rg8snorm': makeNormalizedInfo(kRG, 8, { signed: true, sRGB: false }),
+ 'rg8uint': makeIntegerInfo(kRG, 8, { signed: false }),
+ 'rg8sint': makeIntegerInfo(kRG, 8, { signed: true }),
+ 'r32uint': makeIntegerInfo(kR, 32, { signed: false }),
+ 'r32sint': makeIntegerInfo(kR, 32, { signed: true }),
+ 'r32float': makeFloatInfo(kR, 32),
+ 'rg16uint': makeIntegerInfo(kRG, 16, { signed: false }),
+ 'rg16sint': makeIntegerInfo(kRG, 16, { signed: true }),
+ 'rg16float': makeFloatInfo(kRG, 16),
+ 'rgba8unorm': makeNormalizedInfo(kRGBA, 8, { signed: false, sRGB: false }),
+ 'rgba8unorm-srgb': makeNormalizedInfo(kRGBA, 8, { signed: false, sRGB: true }),
+ 'rgba8snorm': makeNormalizedInfo(kRGBA, 8, { signed: true, sRGB: false }),
+ 'rgba8uint': makeIntegerInfo(kRGBA, 8, { signed: false }),
+ 'rgba8sint': makeIntegerInfo(kRGBA, 8, { signed: true }),
+ 'bgra8unorm': makeNormalizedInfo(kBGRA, 8, { signed: false, sRGB: false }),
+ 'bgra8unorm-srgb': makeNormalizedInfo(kBGRA, 8, { signed: false, sRGB: true }),
+ 'rg32uint': makeIntegerInfo(kRG, 32, { signed: false }),
+ 'rg32sint': makeIntegerInfo(kRG, 32, { signed: true }),
+ 'rg32float': makeFloatInfo(kRG, 32),
+ 'rgba16uint': makeIntegerInfo(kRGBA, 16, { signed: false }),
+ 'rgba16sint': makeIntegerInfo(kRGBA, 16, { signed: true }),
+ 'rgba16float': makeFloatInfo(kRGBA, 16),
+ 'rgba32uint': makeIntegerInfo(kRGBA, 32, { signed: false }),
+ 'rgba32sint': makeIntegerInfo(kRGBA, 32, { signed: true }),
+ 'rgba32float': makeFloatInfo(kRGBA, 32)
+ },
+ ...{
+ rgb10a2uint: {
+ componentOrder: kRGBA,
+ componentInfo: {
+ R: { dataType: 'uint', bitLength: 10 },
+ G: { dataType: 'uint', bitLength: 10 },
+ B: { dataType: 'uint', bitLength: 10 },
+ A: { dataType: 'uint', bitLength: 2 }
+ },
+ encode: (components) => {
+ assertInIntegerRange(components.R, 10, false);
+ assertInIntegerRange(components.G, 10, false);
+ assertInIntegerRange(components.B, 10, false);
+ assertInIntegerRange(components.A, 2, false);
+ return components;
+ },
+ decode: (components) => {
+ assertInIntegerRange(components.R, 10, false);
+ assertInIntegerRange(components.G, 10, false);
+ assertInIntegerRange(components.B, 10, false);
+ assertInIntegerRange(components.A, 2, false);
+ return components;
+ },
+ pack: (components) =>
+ packComponents(
+ kRGBA,
+ components,
+ {
+ R: 10,
+ G: 10,
+ B: 10,
+ A: 2
+ },
+ 'uint'
+ ),
+ unpackBits: (data) =>
+ unpackComponentsBits(kRGBA, data, { R: 10, G: 10, B: 10, A: 2 }),
+ numberToBits: (components) => ({
+ R: components.R & 0x3ff,
+ G: components.G & 0x3ff,
+ B: components.B & 0x3ff,
+ A: components.A & 0x3
+ }),
+ bitsToNumber: (components) => {
+ assertInIntegerRange(components.R, 10, false);
+ assertInIntegerRange(components.G, 10, false);
+ assertInIntegerRange(components.B, 10, false);
+ assertInIntegerRange(components.A, 2, false);
+ return components;
+ },
+ bitsToULPFromZero: (components) => components,
+ numericRange: null
+ },
+ rgb10a2unorm: {
+ componentOrder: kRGBA,
+ componentInfo: {
+ R: { dataType: 'unorm', bitLength: 10 },
+ G: { dataType: 'unorm', bitLength: 10 },
+ B: { dataType: 'unorm', bitLength: 10 },
+ A: { dataType: 'unorm', bitLength: 2 }
+ },
+ encode: (components) => {
+ return {
+ R: floatAsNormalizedInteger(components.R ?? unreachable(), 10, false),
+ G: floatAsNormalizedInteger(components.G ?? unreachable(), 10, false),
+ B: floatAsNormalizedInteger(components.B ?? unreachable(), 10, false),
+ A: floatAsNormalizedInteger(components.A ?? unreachable(), 2, false)
+ };
+ },
+ decode: (components) => {
+ return {
+ R: normalizedIntegerAsFloat(components.R ?? unreachable(), 10, false),
+ G: normalizedIntegerAsFloat(components.G ?? unreachable(), 10, false),
+ B: normalizedIntegerAsFloat(components.B ?? unreachable(), 10, false),
+ A: normalizedIntegerAsFloat(components.A ?? unreachable(), 2, false)
+ };
+ },
+ pack: (components) =>
+ packComponents(
+ kRGBA,
+ components,
+ {
+ R: 10,
+ G: 10,
+ B: 10,
+ A: 2
+ },
+ 'uint'
+ ),
+ unpackBits: (data) =>
+ unpackComponentsBits(kRGBA, data, { R: 10, G: 10, B: 10, A: 2 }),
+ numberToBits: (components) => ({
+ R: floatAsNormalizedInteger(components.R ?? unreachable(), 10, false),
+ G: floatAsNormalizedInteger(components.G ?? unreachable(), 10, false),
+ B: floatAsNormalizedInteger(components.B ?? unreachable(), 10, false),
+ A: floatAsNormalizedInteger(components.A ?? unreachable(), 2, false)
+ }),
+ bitsToNumber: (components) => ({
+ R: normalizedIntegerAsFloat(components.R, 10, false),
+ G: normalizedIntegerAsFloat(components.G, 10, false),
+ B: normalizedIntegerAsFloat(components.B, 10, false),
+ A: normalizedIntegerAsFloat(components.A, 2, false)
+ }),
+ bitsToULPFromZero: (components) => components,
+ numericRange: { min: 0, max: 1 }
+ },
+ rg11b10ufloat: {
+ componentOrder: kRGB,
+ encode: applyEach(identity, kRGB),
+ decode: applyEach(identity, kRGB),
+ componentInfo: {
+ R: { dataType: 'ufloat', bitLength: 11 },
+ G: { dataType: 'ufloat', bitLength: 11 },
+ B: { dataType: 'ufloat', bitLength: 10 }
+ },
+ pack: (components) => {
+ const componentsBits = {
+ R: float32ToFloatBits(components.R ?? unreachable(), 0, 5, 6, 15),
+ G: float32ToFloatBits(components.G ?? unreachable(), 0, 5, 6, 15),
+ B: float32ToFloatBits(components.B ?? unreachable(), 0, 5, 5, 15)
+ };
+ return packComponents(
+ kRGB,
+ componentsBits,
+ {
+ R: 11,
+ G: 11,
+ B: 10
+ },
+ 'uint'
+ );
+ },
+ unpackBits: (data) => unpackComponentsBits(kRGB, data, { R: 11, G: 11, B: 10 }),
+ numberToBits: (components) => ({
+ R: numberToFloatBits(components.R ?? unreachable(), kFloat11Format),
+ G: numberToFloatBits(components.G ?? unreachable(), kFloat11Format),
+ B: numberToFloatBits(components.B ?? unreachable(), kFloat10Format)
+ }),
+ bitsToNumber: (components) => ({
+ R: floatBitsToNumber(components.R, kFloat11Format),
+ G: floatBitsToNumber(components.G, kFloat11Format),
+ B: floatBitsToNumber(components.B, kFloat10Format)
+ }),
+ bitsToULPFromZero: (components) => ({
+ R: floatBitsToNormalULPFromZero(components.R, kFloat11Format),
+ G: floatBitsToNormalULPFromZero(components.G, kFloat11Format),
+ B: floatBitsToNormalULPFromZero(components.B, kFloat10Format)
+ }),
+ numericRange: { min: 0, max: Number.POSITIVE_INFINITY }
+ },
+ rgb9e5ufloat: {
+ componentOrder: kRGB,
+ componentInfo: makePerTexelComponent(kRGB, {
+ dataType: 'ufloat',
+ bitLength: -1 // Components don't really have a bitLength since the format is packed.
+ }),
+ encode: applyEach(identity, kRGB),
+ decode: applyEach(identity, kRGB),
+ pack: (components) =>
+ new Uint32Array([
+ packRGB9E5UFloat(
+ components.R ?? unreachable(),
+ components.G ?? unreachable(),
+ components.B ?? unreachable()
+ )]
+ ).buffer,
+ unpackBits: (data) => {
+ const encoded = data[3] << 24 | data[2] << 16 | data[1] << 8 | data[0];
+ const redMantissa = encoded >>> 0 & 0b111111111;
+ const greenMantissa = encoded >>> 9 & 0b111111111;
+ const blueMantissa = encoded >>> 18 & 0b111111111;
+ const exponentSharedBits = (encoded >>> 27 & 0b11111) << 9;
+ return {
+ R: exponentSharedBits | redMantissa,
+ G: exponentSharedBits | greenMantissa,
+ B: exponentSharedBits | blueMantissa
+ };
+ },
+ numberToBits: (components) => ({
+ R: float32ToFloatBits(components.R ?? unreachable(), 0, 5, 9, 15),
+ G: float32ToFloatBits(components.G ?? unreachable(), 0, 5, 9, 15),
+ B: float32ToFloatBits(components.B ?? unreachable(), 0, 5, 9, 15)
+ }),
+ bitsToNumber: (components) => ({
+ R: ufloatM9E5BitsToNumber(components.R, kUFloat9e5Format),
+ G: ufloatM9E5BitsToNumber(components.G, kUFloat9e5Format),
+ B: ufloatM9E5BitsToNumber(components.B, kUFloat9e5Format)
+ }),
+ bitsToULPFromZero: (components) => ({
+ R: floatBitsToNormalULPFromZero(components.R, kUFloat9e5Format),
+ G: floatBitsToNormalULPFromZero(components.G, kUFloat9e5Format),
+ B: floatBitsToNormalULPFromZero(components.B, kUFloat9e5Format)
+ }),
+ numericRange: { min: 0, max: Number.POSITIVE_INFINITY }
+ },
+ depth32float: makeFloatInfo([TexelComponent.Depth], 32, { restrictedDepth: true }),
+ depth16unorm: makeNormalizedInfo([TexelComponent.Depth], 16, { signed: false, sRGB: false }),
+ depth24plus: {
+ componentOrder: [TexelComponent.Depth],
+ componentInfo: { Depth: { dataType: null, bitLength: 24 } },
+ encode: applyEach(() => unreachable('depth24plus cannot be encoded'), [TexelComponent.Depth]),
+ decode: applyEach(() => unreachable('depth24plus cannot be decoded'), [TexelComponent.Depth]),
+ pack: () => unreachable('depth24plus data cannot be packed'),
+ unpackBits: () => unreachable('depth24plus data cannot be unpacked'),
+ numberToBits: () => unreachable('depth24plus has no representation'),
+ bitsToNumber: () => unreachable('depth24plus has no representation'),
+ bitsToULPFromZero: () => unreachable('depth24plus has no representation'),
+ numericRange: { min: 0, max: 1 }
+ },
+ stencil8: makeIntegerInfo([TexelComponent.Stencil], 8, { signed: false }),
+ 'depth32float-stencil8': {
+ componentOrder: [TexelComponent.Depth, TexelComponent.Stencil],
+ componentInfo: {
+ Depth: {
+ dataType: 'float',
+ bitLength: 32
+ },
+ Stencil: {
+ dataType: 'uint',
+ bitLength: 8
+ }
+ },
+ encode: (components) => {
+ assert(components.Stencil !== undefined);
+ assertInIntegerRange(components.Stencil, 8, false);
+ return components;
+ },
+ decode: (components) => {
+ assert(components.Stencil !== undefined);
+ assertInIntegerRange(components.Stencil, 8, false);
+ return components;
+ },
+ pack: () => unreachable('depth32float-stencil8 data cannot be packed'),
+ unpackBits: () => unreachable('depth32float-stencil8 data cannot be unpacked'),
+ numberToBits: () => unreachable('not implemented'),
+ bitsToNumber: () => unreachable('not implemented'),
+ bitsToULPFromZero: () => unreachable('not implemented'),
+ numericRange: null
+ },
+ 'depth24plus-stencil8': {
+ componentOrder: [TexelComponent.Depth, TexelComponent.Stencil],
+ componentInfo: {
+ Depth: {
+ dataType: null,
+ bitLength: 24
+ },
+ Stencil: {
+ dataType: 'uint',
+ bitLength: 8
+ }
+ },
+ encode: (components) => {
+ assert(components.Depth === undefined, 'depth24plus cannot be encoded');
+ assert(components.Stencil !== undefined);
+ assertInIntegerRange(components.Stencil, 8, false);
+ return components;
+ },
+ decode: (components) => {
+ assert(components.Depth === undefined, 'depth24plus cannot be decoded');
+ assert(components.Stencil !== undefined);
+ assertInIntegerRange(components.Stencil, 8, false);
+ return components;
+ },
+ pack: () => unreachable('depth24plus-stencil8 data cannot be packed'),
+ unpackBits: () => unreachable('depth24plus-stencil8 data cannot be unpacked'),
+ numberToBits: () => unreachable('depth24plus-stencil8 has no representation'),
+ bitsToNumber: () => unreachable('depth24plus-stencil8 has no representation'),
+ bitsToULPFromZero: () => unreachable('depth24plus-stencil8 has no representation'),
+ numericRange: null
+ }
+ }
+};
+
+/**
+ * Get the `ComponentDataType` for a format. All components must have the same type.
+ * @param {UncompressedTextureFormat} format - The input format.
+ * @returns {ComponentDataType} The data of the components.
+ */
+export function getSingleDataType(format) {
+ const infos = Object.values(kTexelRepresentationInfo[format].componentInfo);
+ assert(infos.length > 0);
+ return infos.reduce((acc, cur) => {
+ assert(cur !== undefined);
+ assert(acc === undefined || acc === cur.dataType);
+ return cur.dataType;
+ }, infos[0].dataType);
+}
+
+/**
+ * Get traits for generating code to readback data from a component.
+ * @param {ComponentDataType} dataType - The input component data type.
+ * @returns A dictionary containing the respective `ReadbackTypedArray` and `shaderType`.
+ */
+export function getComponentReadbackTraits(dataType) {
+ switch (dataType) {
+ case 'ufloat':
+ case 'float':
+ case 'unorm':
+ case 'snorm':
+ return {
+ ReadbackTypedArray: Float32Array,
+ shaderType: 'f32'
+ };
+ case 'uint':
+ return {
+ ReadbackTypedArray: Uint32Array,
+ shaderType: 'u32'
+ };
+ case 'sint':
+ return {
+ ReadbackTypedArray: Int32Array,
+ shaderType: 'i32'
+ };
+ default:
+ unreachable();
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.spec.js
new file mode 100644
index 0000000000..d3d0d86fc2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_data.spec.js
@@ -0,0 +1,334 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = 'Test helpers for texel data produce the expected data in the shader';import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert } from '../../../common/util/util.js';
+import {
+ kEncodableTextureFormats,
+ kTextureFormatInfo } from
+
+'../../format_info.js';
+import { GPUTest } from '../../gpu_test.js';
+
+import {
+ kTexelRepresentationInfo,
+ getSingleDataType,
+ getComponentReadbackTraits } from
+'./texel_data.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function doTest(
+t)
+
+
+
+
+
+
+
+
+
+
+{
+ const { format } = t.params;
+ const componentData = t.params.componentData;
+
+ const rep = kTexelRepresentationInfo[format];
+ const texelData = rep.pack(componentData);
+ const texture = t.device.createTexture({
+ format,
+ size: [1, 1, 1],
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
+ });
+
+ t.device.queue.writeTexture(
+ { texture },
+ texelData,
+ {
+ bytesPerRow: texelData.byteLength
+ },
+ [1]
+ );
+
+ const { ReadbackTypedArray, shaderType } = getComponentReadbackTraits(getSingleDataType(format));
+
+ const shader = `
+ @group(0) @binding(0) var tex : texture_2d<${shaderType}>;
+
+ struct Output {
+ ${rep.componentOrder.map((C) => `result${C} : ${shaderType},`).join('\n')}
+ };
+ @group(0) @binding(1) var<storage, read_write> output : Output;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var texel : vec4<${shaderType}> = textureLoad(tex, vec2<i32>(0, 0), 0);
+ ${rep.componentOrder.map((C) => `output.result${C} = texel.${C.toLowerCase()};`).join('\n')}
+ return;
+ }`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: shader
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const outputBuffer = t.device.createBuffer({
+ size: rep.componentOrder.length * 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: texture.createView()
+ },
+ {
+ 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 ReadbackTypedArray(
+ rep.componentOrder.map((c) => {
+ const value = rep.decode(componentData)[c];
+ assert(value !== undefined);
+ return value;
+ })
+ )
+ );
+}
+
+// Make a test parameter by mapping a format and each component to a texel component
+// data value.
+function makeParam(
+format,
+fn)
+{
+ const rep = kTexelRepresentationInfo[format];
+ return {
+ R: rep.componentInfo.R ? fn(rep.componentInfo.R.bitLength, 0) : undefined,
+ G: rep.componentInfo.G ? fn(rep.componentInfo.G.bitLength, 1) : undefined,
+ B: rep.componentInfo.B ? fn(rep.componentInfo.B.bitLength, 2) : undefined,
+ A: rep.componentInfo.A ? fn(rep.componentInfo.A.bitLength, 3) : undefined
+ };
+}
+
+g.test('unorm_texel_data_in_shader').
+params((u) =>
+u.
+combine('format', kEncodableTextureFormats).
+filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return !!info.color && info.color.copyDst && getSingleDataType(format) === 'unorm';
+}).
+beginSubcases().
+expand('componentData', ({ format }) => {
+ const max = (bitLength) => Math.pow(2, bitLength) - 1;
+ return [
+ // Test extrema
+ makeParam(format, () => 0),
+ makeParam(format, (bitLength) => max(bitLength)),
+
+ // Test a middle value
+ makeParam(format, (bitLength) => Math.floor(max(bitLength) / 2)),
+
+ // Test mixed values
+ makeParam(format, (bitLength, i) => {
+ const offset = [0.13, 0.63, 0.42, 0.89];
+ return Math.floor(offset[i] * max(bitLength));
+ })];
+
+})
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.format);
+}).
+fn(doTest);
+
+g.test('snorm_texel_data_in_shader').
+params((u) =>
+u.
+combine('format', kEncodableTextureFormats).
+filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return !!info.color && info.color.copyDst && getSingleDataType(format) === 'snorm';
+}).
+beginSubcases().
+expand('componentData', ({ format }) => {
+ const max = (bitLength) => Math.pow(2, bitLength - 1) - 1;
+ return [
+ // Test extrema
+ makeParam(format, () => 0),
+ makeParam(format, (bitLength) => max(bitLength)),
+ makeParam(format, (bitLength) => -max(bitLength)),
+ makeParam(format, (bitLength) => -max(bitLength) - 1),
+
+ // Test a middle value
+ makeParam(format, (bitLength) => Math.floor(max(bitLength) / 2)),
+
+ // Test mixed values
+ makeParam(format, (bitLength, i) => {
+ const offset = [0.13, 0.63, 0.42, 0.89];
+ const range = 2 * max(bitLength);
+ return -max(bitLength) + Math.floor(offset[i] * range);
+ })];
+
+})
+).
+fn(doTest);
+
+g.test('uint_texel_data_in_shader').
+params((u) =>
+u.
+combine('format', kEncodableTextureFormats).
+filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return !!info.color && info.color.copyDst && getSingleDataType(format) === 'uint';
+}).
+beginSubcases().
+expand('componentData', ({ format }) => {
+ const max = (bitLength) => Math.pow(2, bitLength) - 1;
+ return [
+ // Test extrema
+ makeParam(format, () => 0),
+ makeParam(format, (bitLength) => max(bitLength)),
+
+ // Test a middle value
+ makeParam(format, (bitLength) => Math.floor(max(bitLength) / 2)),
+
+ // Test mixed values
+ makeParam(format, (bitLength, i) => {
+ const offset = [0.13, 0.63, 0.42, 0.89];
+ return Math.floor(offset[i] * max(bitLength));
+ })];
+
+})
+).
+fn(doTest);
+
+g.test('sint_texel_data_in_shader').
+params((u) =>
+u.
+combine('format', kEncodableTextureFormats).
+filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return !!info.color && info.color.copyDst && getSingleDataType(format) === 'sint';
+}).
+beginSubcases().
+expand('componentData', ({ format }) => {
+ const max = (bitLength) => Math.pow(2, bitLength - 1) - 1;
+ return [
+ // Test extrema
+ makeParam(format, () => 0),
+ makeParam(format, (bitLength) => max(bitLength)),
+ makeParam(format, (bitLength) => -max(bitLength) - 1),
+
+ // Test a middle value
+ makeParam(format, (bitLength) => Math.floor(max(bitLength) / 2)),
+
+ // Test mixed values
+ makeParam(format, (bitLength, i) => {
+ const offset = [0.13, 0.63, 0.42, 0.89];
+ const range = 2 * max(bitLength);
+ return -max(bitLength) + Math.floor(offset[i] * range);
+ })];
+
+})
+).
+fn(doTest);
+
+g.test('float_texel_data_in_shader').
+desc(
+ `
+TODO: Test NaN, Infinity, -Infinity [1]`
+).
+params((u) =>
+u.
+combine('format', kEncodableTextureFormats).
+filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return !!info.color && info.color.copyDst && getSingleDataType(format) === 'float';
+}).
+beginSubcases().
+expand('componentData', ({ format }) => {
+ return [
+ // Test extrema
+ makeParam(format, () => 0),
+
+ // [1]: Test NaN, Infinity, -Infinity
+
+ // Test some values
+ makeParam(format, () => 0.1199951171875),
+ makeParam(format, () => 1.4072265625),
+ makeParam(format, () => 24928),
+ makeParam(format, () => -0.1319580078125),
+ makeParam(format, () => -323.25),
+ makeParam(format, () => -7440),
+
+ // Test mixed values
+ makeParam(format, (bitLength, i) => {
+ return [24896, -0.1319580078125, -323.25, -234.375][i];
+ })];
+
+})
+).
+fn(doTest);
+
+g.test('ufloat_texel_data_in_shader').
+desc(
+ `
+TODO: Test NaN, Infinity [1]`
+).
+params((u) =>
+u.
+combine('format', kEncodableTextureFormats).
+filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return !!info.color && info.color.copyDst && getSingleDataType(format) === 'ufloat';
+}).
+beginSubcases().
+expand('componentData', ({ format }) => {
+ return [
+ // Test extrema
+ makeParam(format, () => 0),
+
+ // [2]: Test NaN, Infinity
+
+ // Test some values
+ makeParam(format, () => 0.119140625),
+ makeParam(format, () => 1.40625),
+ makeParam(format, () => 24896),
+
+ // Test scattered mixed values
+ makeParam(format, (bitLength, i) => {
+ return [24896, 1.40625, 0.119140625, 0.23095703125][i];
+ }),
+
+ // Test mixed values that are close in magnitude.
+ makeParam(format, (bitLength, i) => {
+ return [0.1337890625, 0.17919921875, 0.119140625, 0.125][i];
+ })];
+
+})
+).
+fn(doTest); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_view.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_view.js
new file mode 100644
index 0000000000..d5b0bd4b2a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texel_view.js
@@ -0,0 +1,201 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, memcpy } from '../../../common/util/util.js';import { kTextureFormatInfo } from '../../format_info.js';import { generatePrettyTable } from '../pretty_diff_tables.js';
+import { reifyExtent3D, reifyOrigin3D } from '../unions.js';
+
+import { fullSubrectCoordinates } from './base.js';
+import { kTexelRepresentationInfo, makeClampToRange } from './texel_data.js';
+
+/** Function taking some x,y,z coordinates and returning `Readonly<T>`. */
+
+
+/**
+ * Wrapper to view various representations of texture data in other ways. E.g., can:
+ * - Provide a mapped buffer, containing copied texture data, and read color values.
+ * - Provide a function that generates color values by coordinate, and convert to ULPs-from-zero.
+ *
+ * MAINTENANCE_TODO: Would need some refactoring to support block formats, which could be partially
+ * supported if useful.
+ */
+export class TexelView {
+ /** The GPUTextureFormat of the TexelView. */
+
+ /** Generates the bytes for the texel at the given coordinates. */
+
+ /** Generates the ULPs-from-zero for the texel at the given coordinates. */
+
+ /** Generates the color for the texel at the given coordinates. */
+
+
+ constructor(
+ format,
+ {
+ bytes,
+ ulpFromZero,
+ color
+
+
+
+
+ })
+ {
+ this.format = format;
+ this.bytes = bytes;
+ this.ulpFromZero = ulpFromZero;
+ this.color = color;
+ }
+
+ /**
+ * Produces a TexelView from "linear image data", i.e. the `writeTexture` format. Takes a
+ * reference to the input `subrectData`, so any changes to it will be visible in the TexelView.
+ */
+ static fromTextureDataByReference(
+ format,
+ subrectData,
+ {
+ bytesPerRow,
+ rowsPerImage,
+ subrectOrigin,
+ subrectSize
+
+
+
+
+
+ })
+ {
+ const origin = reifyOrigin3D(subrectOrigin);
+ const size = reifyExtent3D(subrectSize);
+
+ const info = kTextureFormatInfo[format];
+ assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');
+
+ return TexelView.fromTexelsAsBytes(format, (coords) => {
+ assert(
+ coords.x >= origin.x &&
+ coords.y >= origin.y &&
+ coords.z >= origin.z &&
+ coords.x < origin.x + size.width &&
+ coords.y < origin.y + size.height &&
+ coords.z < origin.z + size.depthOrArrayLayers,
+ () => `coordinate (${coords.x},${coords.y},${coords.z}) out of bounds`
+ );
+
+ const imageOffsetInRows = (coords.z - origin.z) * rowsPerImage;
+ const rowOffset = (imageOffsetInRows + (coords.y - origin.y)) * bytesPerRow;
+ const offset = rowOffset + (coords.x - origin.x) * info.bytesPerBlock;
+
+ // MAINTENANCE_TODO: To support block formats, decode the block and then index into the result.
+ return subrectData.subarray(offset, offset + info.bytesPerBlock);
+ });
+ }
+
+ /** Produces a TexelView from a generator of bytes for individual texel blocks. */
+ static fromTexelsAsBytes(
+ format,
+ generator)
+ {
+ const info = kTextureFormatInfo[format];
+ assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');
+
+ const repr = kTexelRepresentationInfo[format];
+ return new TexelView(format, {
+ bytes: generator,
+ ulpFromZero: (coords) => repr.bitsToULPFromZero(repr.unpackBits(generator(coords))),
+ color: (coords) => repr.bitsToNumber(repr.unpackBits(generator(coords)))
+ });
+ }
+
+ /** Produces a TexelView from a generator of numeric "color" values for each texel. */
+ static fromTexelsAsColors(
+ format,
+ generator,
+ { clampToFormatRange = false } = {})
+ {
+ const info = kTextureFormatInfo[format];
+ assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');
+
+ if (clampToFormatRange) {
+ const applyClamp = makeClampToRange(format);
+ const oldGenerator = generator;
+ generator = (coords) => applyClamp(oldGenerator(coords));
+ }
+
+ const repr = kTexelRepresentationInfo[format];
+ return new TexelView(format, {
+ bytes: (coords) => new Uint8Array(repr.pack(repr.encode(generator(coords)))),
+ ulpFromZero: (coords) => repr.bitsToULPFromZero(repr.numberToBits(generator(coords))),
+ color: generator
+ });
+ }
+
+ /** Writes the contents of a TexelView as "linear image data", i.e. the `writeTexture` format. */
+ writeTextureData(
+ subrectData,
+ {
+ bytesPerRow,
+ rowsPerImage,
+ subrectOrigin: subrectOrigin_,
+ subrectSize: subrectSize_
+
+
+
+
+
+ })
+ {
+ const subrectOrigin = reifyOrigin3D(subrectOrigin_);
+ const subrectSize = reifyExtent3D(subrectSize_);
+
+ const info = kTextureFormatInfo[this.format];
+ assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');
+
+ for (let z = subrectOrigin.z; z < subrectOrigin.z + subrectSize.depthOrArrayLayers; ++z) {
+ for (let y = subrectOrigin.y; y < subrectOrigin.y + subrectSize.height; ++y) {
+ for (let x = subrectOrigin.x; x < subrectOrigin.x + subrectSize.width; ++x) {
+ const start = (z * rowsPerImage + y) * bytesPerRow + x * info.bytesPerBlock;
+ memcpy({ src: this.bytes({ x, y, z }) }, { dst: subrectData, start });
+ }
+ }
+ }
+ }
+
+ /** Returns a pretty table string of the given coordinates and their values. */
+ // MAINTENANCE_TODO: Unify some internal helpers with those in texture_ok.ts.
+ toString(subrectOrigin, subrectSize) {
+ const info = kTextureFormatInfo[this.format];
+ const repr = kTexelRepresentationInfo[this.format];
+
+ const integerSampleType = info.sampleType === 'uint' || info.sampleType === 'sint';
+ const numberToString = integerSampleType ?
+ (n) => n.toFixed() :
+ (n) => n.toPrecision(6);
+
+ const componentOrderStr = repr.componentOrder.join(',') + ':';
+ const subrectCoords = [...fullSubrectCoordinates(subrectOrigin, subrectSize)];
+
+ const printCoords = function* () {
+ yield* [' coords', '==', 'X,Y,Z:'];
+ for (const coords of subrectCoords) yield `${coords.x},${coords.y},${coords.z}`;
+ }();
+ const printActualBytes = function* (t) {
+ yield* [' act. texel bytes (little-endian)', '==', '0x:'];
+ for (const coords of subrectCoords) {
+ yield Array.from(t.bytes(coords), (b) => b.toString(16).padStart(2, '0')).join(' ');
+ }
+ }(this);
+ const printActualColors = function* (t) {
+ yield* [' act. colors', '==', componentOrderStr];
+ for (const coords of subrectCoords) {
+ const pixel = t.color(coords);
+ yield `${repr.componentOrder.map((ch) => numberToString(pixel[ch])).join(',')}`;
+ }
+ }(this);
+
+ const opts = {
+ fillToWidth: 120,
+ numberToString
+ };
+ return `${generatePrettyTable(opts, [printCoords, printActualBytes, printActualColors])}`;
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.js
new file mode 100644
index 0000000000..c2047df878
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.js
@@ -0,0 +1,348 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, ErrorWithExtra, unreachable } from '../../../common/util/util.js';import { kTextureFormatInfo } from '../../format_info.js';
+import { numbersApproximatelyEqual } from '../conversion.js';
+import { generatePrettyTable } from '../pretty_diff_tables.js';
+import { reifyExtent3D, reifyOrigin3D } from '../unions.js';
+
+import { fullSubrectCoordinates } from './base.js';
+import { getTextureSubCopyLayout } from './layout.js';
+import { kTexelRepresentationInfo } from './texel_data.js';
+import { TexelView } from './texel_view.js';
+
+
+
+/** Threshold options for comparing texels of different formats (norm/float/int). */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+function makeTexelViewComparer(
+format,
+{ actTexelView, expTexelView },
+opts)
+{
+ const {
+ maxIntDiff = 0,
+ maxFractionalDiff,
+ maxDiffULPsForNormFormat,
+ maxDiffULPsForFloatFormat
+ } = opts;
+
+ assert(maxIntDiff >= 0, 'threshold must be non-negative');
+ if (maxFractionalDiff !== undefined) {
+ assert(maxFractionalDiff >= 0, 'threshold must be non-negative');
+ }
+ if (maxDiffULPsForFloatFormat !== undefined) {
+ assert(maxDiffULPsForFloatFormat >= 0, 'threshold must be non-negative');
+ }
+ if (maxDiffULPsForNormFormat !== undefined) {
+ assert(maxDiffULPsForNormFormat >= 0, 'threshold must be non-negative');
+ }
+
+ const fmtIsInt = format.includes('int');
+ const fmtIsNorm = format.includes('norm');
+ const fmtIsFloat = format.includes('float');
+
+ const tvc = {};
+ if (fmtIsInt) {
+ tvc.predicate = (coords) =>
+ comparePerComponent(actTexelView.color(coords), expTexelView.color(coords), maxIntDiff);
+ } else if (fmtIsNorm && maxDiffULPsForNormFormat !== undefined) {
+ tvc.predicate = (coords) =>
+ comparePerComponent(
+ actTexelView.ulpFromZero(coords),
+ expTexelView.ulpFromZero(coords),
+ maxDiffULPsForNormFormat
+ );
+ } else if (fmtIsFloat && maxDiffULPsForFloatFormat !== undefined) {
+ tvc.predicate = (coords) =>
+ comparePerComponent(
+ actTexelView.ulpFromZero(coords),
+ expTexelView.ulpFromZero(coords),
+ maxDiffULPsForFloatFormat
+ );
+ } else if (maxFractionalDiff !== undefined) {
+ tvc.predicate = (coords) =>
+ comparePerComponent(
+ actTexelView.color(coords),
+ expTexelView.color(coords),
+ maxFractionalDiff
+ );
+ } else {
+ if (fmtIsNorm) {
+ unreachable('need maxFractionalDiff or maxDiffULPsForNormFormat to compare norm textures');
+ } else if (fmtIsFloat) {
+ unreachable('need maxFractionalDiff or maxDiffULPsForFloatFormat to compare float textures');
+ } else {
+ unreachable();
+ }
+ }
+
+ const repr = kTexelRepresentationInfo[format];
+ if (fmtIsInt) {
+ tvc.tableRows = (failedCoords) => [
+ [`tolerance ± ${maxIntDiff}`],
+ function* () {
+ yield* [` diff (act - exp)`, '==', ''];
+ for (const coords of failedCoords) {
+ const act = actTexelView.color(coords);
+ const exp = expTexelView.color(coords);
+ yield repr.componentOrder.map((ch) => act[ch] - exp[ch]).join(',');
+ }
+ }()];
+
+ } else if (
+ fmtIsNorm && maxDiffULPsForNormFormat !== undefined ||
+ fmtIsFloat && maxDiffULPsForFloatFormat !== undefined)
+ {
+ const toleranceULPs = fmtIsNorm ? maxDiffULPsForNormFormat : maxDiffULPsForFloatFormat;
+ tvc.tableRows = (failedCoords) => [
+ [`tolerance ± ${toleranceULPs} normal-ULPs`],
+ function* () {
+ yield* [` diff (act - exp) in normal-ULPs`, '==', ''];
+ for (const coords of failedCoords) {
+ const act = actTexelView.ulpFromZero(coords);
+ const exp = expTexelView.ulpFromZero(coords);
+ yield repr.componentOrder.map((ch) => act[ch] - exp[ch]).join(',');
+ }
+ }()];
+
+ } else {
+ assert(maxFractionalDiff !== undefined);
+ tvc.tableRows = (failedCoords) => [
+ [`tolerance ± ${maxFractionalDiff}`],
+ function* () {
+ yield* [` diff (act - exp)`, '==', ''];
+ for (const coords of failedCoords) {
+ const act = actTexelView.color(coords);
+ const exp = expTexelView.color(coords);
+ yield repr.componentOrder.map((ch) => (act[ch] - exp[ch]).toPrecision(4)).join(',');
+ }
+ }()];
+
+ }
+
+ return tvc;
+}
+
+function comparePerComponent(
+actual,
+expected,
+maxDiff)
+{
+ return Object.keys(actual).every((key) => {
+ const k = key;
+ const act = actual[k];
+ const exp = expected[k];
+ if (exp === undefined) return false;
+ return numbersApproximatelyEqual(act, exp, maxDiff);
+ });
+}
+
+/** Create a new mappable GPUBuffer, and copy a subrectangle of GPUTexture data into it. */
+function createTextureCopyForMapRead(
+t,
+source,
+copySize,
+{ format })
+{
+ const { byteLength, bytesPerRow, rowsPerImage } = getTextureSubCopyLayout(format, copySize, {
+ aspect: source.aspect
+ });
+
+ const buffer = t.device.createBuffer({
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
+ size: byteLength
+ });
+ t.trackForCleanup(buffer);
+
+ const cmd = t.device.createCommandEncoder();
+ cmd.copyTextureToBuffer(source, { buffer, bytesPerRow, rowsPerImage }, copySize);
+ t.device.queue.submit([cmd.finish()]);
+
+ return { buffer, bytesPerRow, rowsPerImage };
+}
+
+export function findFailedPixels(
+format,
+subrectOrigin,
+subrectSize,
+{ actTexelView, expTexelView },
+texelCompareOptions,
+coords)
+{
+ const comparer = makeTexelViewComparer(
+ format,
+ { actTexelView, expTexelView },
+ texelCompareOptions
+ );
+
+ const lowerCorner = [subrectSize.width, subrectSize.height, subrectSize.depthOrArrayLayers];
+ const upperCorner = [0, 0, 0];
+ const failedPixels = [];
+ for (const coord of coords ?? fullSubrectCoordinates(subrectOrigin, subrectSize)) {
+ const { x, y, z } = coord;
+ if (!comparer.predicate(coord)) {
+ failedPixels.push(coord);
+ lowerCorner[0] = Math.min(lowerCorner[0], x);
+ lowerCorner[1] = Math.min(lowerCorner[1], y);
+ lowerCorner[2] = Math.min(lowerCorner[2], z);
+ upperCorner[0] = Math.max(upperCorner[0], x);
+ upperCorner[1] = Math.max(upperCorner[1], y);
+ upperCorner[2] = Math.max(upperCorner[2], z);
+ }
+ }
+ if (failedPixels.length === 0) {
+ return undefined;
+ }
+
+ const info = kTextureFormatInfo[format];
+ const repr = kTexelRepresentationInfo[format];
+
+ const integerSampleType = info.sampleType === 'uint' || info.sampleType === 'sint';
+ const numberToString = integerSampleType ?
+ (n) => n.toFixed() :
+ (n) => n.toPrecision(6);
+
+ const componentOrderStr = repr.componentOrder.join(',') + ':';
+
+ const printCoords = function* () {
+ yield* [' coords', '==', 'X,Y,Z:'];
+ for (const coords of failedPixels) yield `${coords.x},${coords.y},${coords.z}`;
+ }();
+ const printActualBytes = function* () {
+ yield* [' act. texel bytes (little-endian)', '==', '0x:'];
+ for (const coords of failedPixels) {
+ yield Array.from(actTexelView.bytes(coords), (b) => b.toString(16).padStart(2, '0')).join(' ');
+ }
+ }();
+ const printActualColors = function* () {
+ yield* [' act. colors', '==', componentOrderStr];
+ for (const coords of failedPixels) {
+ const pixel = actTexelView.color(coords);
+ yield `${repr.componentOrder.map((ch) => numberToString(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(',')}`;
+ }
+ }();
+ const printActualULPs = function* () {
+ yield* [' act. normal-ULPs-from-zero', '==', componentOrderStr];
+ for (const coords of failedPixels) {
+ const pixel = actTexelView.ulpFromZero(coords);
+ yield `${repr.componentOrder.map((ch) => pixel[ch]).join(',')}`;
+ }
+ }();
+ const printExpectedULPs = function* () {
+ yield* [` exp. normal-ULPs-from-zero`, '==', componentOrderStr];
+ for (const coords of failedPixels) {
+ const pixel = expTexelView.ulpFromZero(coords);
+ yield `${repr.componentOrder.map((ch) => pixel[ch]).join(',')}`;
+ }
+ }();
+
+ const opts = {
+ fillToWidth: 120,
+ numberToString
+ };
+ return `\
+ between ${lowerCorner} and ${upperCorner} inclusive:
+${generatePrettyTable(opts, [
+ printCoords,
+ printActualBytes,
+ printActualColors,
+ printExpectedColors,
+ printActualULPs,
+ printExpectedULPs,
+ ...comparer.tableRows(failedPixels)]
+ )}`;
+}
+
+/**
+ * Check the contents of a GPUTexture by reading it back (with copyTextureToBuffer+mapAsync), then
+ * comparing the data with the data in `expTexelView`.
+ *
+ * The actual and expected texture data are both converted to the "NormalULPFromZero" format,
+ * which is a signed number representing how far the number is from zero, in ULPs, skipping
+ * subnormal numbers (where ULP is defined for float, normalized, and integer formats).
+ */
+export async function textureContentIsOKByT2B(
+t,
+source,
+copySize_,
+{ expTexelView },
+texelCompareOptions,
+coords)
+{
+ const subrectOrigin = reifyOrigin3D(source.origin ?? [0, 0, 0]);
+ const subrectSize = reifyExtent3D(copySize_);
+ const format = expTexelView.format;
+
+ const { buffer, bytesPerRow, rowsPerImage } = createTextureCopyForMapRead(
+ t,
+ source,
+ subrectSize,
+ { format }
+ );
+
+ await buffer.mapAsync(GPUMapMode.READ);
+ const data = new Uint8Array(buffer.getMappedRange());
+
+ const texelViewConfig = {
+ bytesPerRow,
+ rowsPerImage,
+ subrectOrigin,
+ subrectSize
+ };
+
+ const actTexelView = TexelView.fromTextureDataByReference(format, data, texelViewConfig);
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ subrectOrigin,
+ subrectSize,
+ { actTexelView, expTexelView },
+ texelCompareOptions,
+ coords
+ );
+
+ if (failedPixelsMessage === undefined) {
+ return undefined;
+ }
+
+ const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
+ return new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ // Make a new TexelView with a copy of the data so we can unmap the buffer (debug mode only).
+ actTexelView: TexelView.fromTextureDataByReference(format, data.slice(), texelViewConfig)
+ }));
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.spec.js
new file mode 100644
index 0000000000..76198cb3b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/texture/texture_ok.spec.js
@@ -0,0 +1,159 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = 'checkPixels helpers behave as expected against real textures';import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUTest } from '../../gpu_test.js';
+
+import { TexelView } from './texel_view.js';
+import { textureContentIsOKByT2B } from './texture_ok.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('float32').
+desc(`Basic test that actual/expected must match, for float32.`).
+params((u) =>
+u.
+combineWithParams([
+{ format: 'rgba32float' }, //
+{ format: 'rg32float' }]
+).
+beginSubcases().
+combineWithParams([
+// Expected data is 0.6 in all channels
+{ data: 0.6, opts: { maxFractionalDiff: 0.0000001 }, _ok: true },
+{ data: 0.6, opts: { maxDiffULPsForFloatFormat: 1 }, _ok: true },
+
+{ data: 0.5999, opts: { maxFractionalDiff: 0 }, _ok: false },
+{ data: 0.5999, opts: { maxFractionalDiff: 0.0001001 }, _ok: true },
+
+{ data: 0.6001, opts: { maxFractionalDiff: 0 }, _ok: false },
+{ data: 0.6001, opts: { maxFractionalDiff: 0.0001001 }, _ok: true },
+
+{ data: 0.5999, opts: { maxDiffULPsForFloatFormat: 1677 }, _ok: false },
+{ data: 0.5999, opts: { maxDiffULPsForFloatFormat: 1678 }, _ok: true },
+
+{ data: 0.6001, opts: { maxDiffULPsForFloatFormat: 1676 }, _ok: false },
+{ data: 0.6001, opts: { maxDiffULPsForFloatFormat: 1677 }, _ok: true }]
+)
+).
+fn(async (t) => {
+ const { format, data, opts, _ok } = t.params;
+
+ const size = [1, 1];
+ const texture = t.device.createTexture({
+ format,
+ size,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
+ });
+ t.trackForCleanup(texture);
+ t.device.queue.writeTexture({ texture }, new Float32Array([data, data, data, data]), {}, size);
+
+ const expColor = { R: 0.6, G: 0.6, B: 0.6, A: 0.6 };
+ const expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => expColor);
+
+ const result = await textureContentIsOKByT2B(t, { texture }, size, { expTexelView }, opts);
+ t.expect(result === undefined === _ok, `expected ${_ok}, got ${result === undefined}`);
+});
+
+g.test('norm').
+desc(`Basic test that actual/expected must match, for unorm/snorm.`).
+params((u) =>
+u.
+combine('mode', ['bytes', 'colors']).
+combineWithParams([
+{ format: 'r8unorm', _maxValue: 255 },
+{ format: 'r8snorm', _maxValue: 127 }]
+).
+beginSubcases().
+combineWithParams([
+// Expected data is [10, 10]
+{ data: [10, 10], _ok: true },
+{ data: [10, 11], _ok: false },
+{ data: [11, 10], _ok: false },
+{ data: [11, 11], _ok: false }]
+)
+).
+fn(async (t) => {
+ const { mode, format, _maxValue, data, _ok } = t.params;
+
+ const size = [2, 1];
+ const texture = t.device.createTexture({
+ format,
+ size,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
+ });
+ t.trackForCleanup(texture);
+ t.device.queue.writeTexture({ texture }, new Int8Array(data), {}, size);
+
+ let expTexelView;
+ switch (mode) {
+ case 'bytes':
+ expTexelView = TexelView.fromTexelsAsBytes(format, (_coords) => new Uint8Array([10]));
+ break;
+ case 'colors':
+ expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => ({ R: 10 / _maxValue }));
+ break;
+ }
+
+ const result = await textureContentIsOKByT2B(
+ t,
+ { texture },
+ size,
+ { expTexelView },
+ { maxDiffULPsForNormFormat: 0 }
+ );
+ t.expect(result === undefined === _ok, result?.message);
+});
+
+g.test('snorm_min').
+desc(
+ `The minimum snorm value has two possible representations (e.g. -127 and -128). Ensure that
+ actual/expected can mismatch in both directions and pass the test.`
+).
+params((u) =>
+u //
+.combine('mode', ['bytes', 'colors']).
+combineWithParams([
+//
+{ format: 'r8snorm', _maxValue: 127 }]
+)
+).
+fn(async (t) => {
+ const { mode, format, _maxValue } = t.params;
+
+ const data = [-_maxValue, -_maxValue - 1];
+
+ const size = [2, 1];
+ const texture = t.device.createTexture({
+ format,
+ size,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
+ });
+ t.trackForCleanup(texture);
+ t.device.queue.writeTexture({ texture }, new Int8Array(data), {}, size);
+
+ let expTexelView;
+ switch (mode) {
+ case 'bytes':
+ {
+ // Actual value should be [-127,-128], expected value is [-128,-127], both should pass.
+ const exp = [-_maxValue - 1, -_maxValue];
+ expTexelView = TexelView.fromTexelsAsBytes(
+ format,
+ (coords) => new Uint8Array([exp[coords.x]])
+ );
+ }
+ break;
+ case 'colors':
+ expTexelView = TexelView.fromTexelsAsColors(format, (_coords) => ({ R: -1 }));
+ break;
+ }
+
+ const result = await textureContentIsOKByT2B(
+ t,
+ { texture },
+ size,
+ { expTexelView },
+ { maxDiffULPsForNormFormat: 0 }
+ );
+ t.expectOK(result);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/util/unions.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/unions.js
new file mode 100644
index 0000000000..0f85078607
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/util/unions.js
@@ -0,0 +1,45 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ /**
+ * Reifies a `GPUOrigin3D` into a `Required<GPUOrigin3DDict>`.
+ */export function reifyOrigin3D(val)
+{
+ if (Symbol.iterator in val) {
+ const v = Array.from(val);
+ return {
+ x: (v[0] ?? 0) | 0,
+ y: (v[1] ?? 0) | 0,
+ z: (v[2] ?? 0) | 0
+ };
+ } else {
+ const v = val;
+ return {
+ x: (v.x ?? 0) | 0,
+ y: (v.y ?? 0) | 0,
+ z: (v.z ?? 0) | 0
+ };
+ }
+}
+
+/**
+ * Reifies a `GPUExtent3D` into a `Required<GPUExtent3DDict>`.
+ */
+export function reifyExtent3D(
+val)
+{
+ if (Symbol.iterator in val) {
+ const v = Array.from(val);
+ return {
+ width: (v[0] ?? 1) | 0,
+ height: (v[1] ?? 1) | 0,
+ depthOrArrayLayers: (v[2] ?? 1) | 0
+ };
+ } else {
+ const v = val;
+ return {
+ width: (v.width ?? 1) | 0,
+ height: (v.height ?? 1) | 0,
+ depthOrArrayLayers: (v.depthOrArrayLayers ?? 1) | 0
+ };
+ }
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/configure.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/configure.spec.js
new file mode 100644
index 0000000000..30acc8c9a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/configure.spec.js
@@ -0,0 +1,426 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for GPUCanvasContext.configure.
+
+TODO:
+- Test colorSpace
+- Test viewFormats
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert } from '../../../common/util/util.js';
+import { kCanvasTextureFormats, kTextureUsages } from '../../capability_info.js';
+import { GPUConst } from '../../constants.js';
+import {
+ kAllTextureFormats,
+ kFeaturesForFormats,
+ kTextureFormats,
+ filterFormatsByFeature,
+ viewCompatible } from
+'../../format_info.js';
+import { GPUTest } from '../../gpu_test.js';
+import { kAllCanvasTypes, createCanvas } from '../../util/create_elements.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('defaults').
+desc(
+ `
+ Ensure that the defaults for GPUCanvasConfiguration are correct.
+ `
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes)
+).
+fn((t) => {
+ const { canvasType } = t.params;
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: t.device,
+ format: 'rgba8unorm'
+ });
+
+ const currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture.format === 'rgba8unorm');
+ t.expect(currentTexture.usage === GPUTextureUsage.RENDER_ATTACHMENT);
+ t.expect(currentTexture.dimension === '2d');
+ t.expect(currentTexture.width === canvas.width);
+ t.expect(currentTexture.height === canvas.height);
+ t.expect(currentTexture.depthOrArrayLayers === 1);
+ t.expect(currentTexture.mipLevelCount === 1);
+ t.expect(currentTexture.sampleCount === 1);
+});
+
+g.test('device').
+desc(
+ `
+ Ensure that configure reacts appropriately to various device states.
+ `
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes)
+).
+fn((t) => {
+ const { canvasType } = t.params;
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ // Calling configure without a device should throw a TypeError.
+ t.shouldThrow('TypeError', () => {
+ ctx.configure({
+ format: 'rgba8unorm'
+ });
+ });
+
+ // Device is not configured, so getCurrentTexture will throw an InvalidStateError.
+ t.shouldThrow('InvalidStateError', () => {
+ ctx.getCurrentTexture();
+ });
+
+ // Calling configure with a device should succeed.
+ ctx.configure({
+ device: t.device,
+ format: 'rgba8unorm'
+ });
+
+ // getCurrentTexture will succeed with a valid device.
+ ctx.getCurrentTexture();
+
+ // Unconfiguring should cause the device to be cleared.
+ ctx.unconfigure();
+ t.shouldThrow('InvalidStateError', () => {
+ ctx.getCurrentTexture();
+ });
+
+ // Should be able to successfully configure again after unconfiguring.
+ ctx.configure({
+ device: t.device,
+ format: 'rgba8unorm'
+ });
+ ctx.getCurrentTexture();
+});
+
+g.test('format').
+desc(
+ `
+ Ensure that only valid texture formats are allowed when calling configure.
+ `
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+combine('format', kAllTextureFormats)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+}).
+fn((t) => {
+ const { canvasType, format } = t.params;
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ // Would prefer to use kCanvasTextureFormats.includes(format), but that's giving TS errors.
+ let validFormat = false;
+ for (const canvasFormat of kCanvasTextureFormats) {
+ if (format === canvasFormat) {
+ validFormat = true;
+ break;
+ }
+ }
+
+ t.expectValidationError(() => {
+ ctx.configure({
+ device: t.device,
+ format
+ });
+ }, !validFormat);
+
+ t.expectValidationError(() => {
+ // Should always return a texture, whether the configured format was valid or not.
+ const currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture instanceof GPUTexture);
+ }, !validFormat);
+});
+
+g.test('usage').
+desc(
+ `
+ Ensure that getCurrentTexture returns a texture with the configured usages.
+ `
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+beginSubcases().
+expand('usage', () => {
+ const usageSet = new Set();
+ for (const usage0 of kTextureUsages) {
+ for (const usage1 of kTextureUsages) {
+ usageSet.add(usage0 | usage1);
+ }
+ }
+ return usageSet;
+})
+).
+fn((t) => {
+ const { canvasType, usage } = t.params;
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: t.device,
+ format: 'rgba8unorm',
+ usage
+ });
+
+ const currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture instanceof GPUTexture);
+ t.expect(currentTexture.usage === usage);
+
+ // Try to use the texture with the given usage
+
+ if (usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) {
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: currentTexture.createView(),
+ clearValue: [1.0, 0.0, 0.0, 1.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ if (usage & GPUConst.TextureUsage.TEXTURE_BINDING) {
+ const bgl = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: {}
+ }]
+
+ });
+
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ {
+ binding: 0,
+ resource: currentTexture.createView()
+ }]
+
+ });
+ }
+
+ if (usage & GPUConst.TextureUsage.STORAGE_BINDING) {
+ const bgl = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ storageTexture: { access: 'write-only', format: currentTexture.format }
+ }]
+
+ });
+
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [
+ {
+ binding: 0,
+ resource: currentTexture.createView()
+ }]
+
+ });
+ }
+
+ if (usage & GPUConst.TextureUsage.COPY_DST) {
+ const rgbaData = new Uint8Array([255, 0, 0, 255]);
+
+ t.device.queue.writeTexture({ texture: currentTexture }, rgbaData, {}, [1, 1, 1]);
+ }
+
+ if (usage & GPUConst.TextureUsage.COPY_SRC) {
+ const size = [currentTexture.width, currentTexture.height, 1];
+ const dstTexture = t.device.createTexture({
+ format: currentTexture.format,
+ usage: GPUTextureUsage.COPY_DST,
+ size
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture({ texture: currentTexture }, { texture: dstTexture }, size);
+ t.device.queue.submit([encoder.finish()]);
+ }
+});
+
+g.test('alpha_mode').
+desc(
+ `
+ Ensure that all valid alphaMode values are allowed when calling configure.
+ `
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+beginSubcases().
+combine('alphaMode', ['opaque', 'premultiplied'])
+).
+fn((t) => {
+ const { canvasType, alphaMode } = t.params;
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: t.device,
+ format: 'rgba8unorm',
+ alphaMode
+ });
+
+ const currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture instanceof GPUTexture);
+});
+
+g.test('size_zero_before_configure').
+desc(`Ensure a validation error is raised in configure() if the size of the canvas is zero.`).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+combine('zeroDimension', ['width', 'height'])
+).
+fn((t) => {
+ const { canvasType, zeroDimension } = t.params;
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ canvas[zeroDimension] = 0;
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ // Validation error, the canvas size is 0 which doesn't make a valid GPUTextureDescriptor.
+ t.expectValidationError(() => {
+ ctx.configure({
+ device: t.device,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ });
+
+ canvas[zeroDimension] = 1;
+
+ // The size being incorrect doesn't make for an invalid configuration. Now that it is fixed
+ // getting textures from the canvas should work.
+ const currentTexture = ctx.getCurrentTexture();
+
+ // Try rendering to it even!
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: currentTexture.createView(),
+ clearValue: [1.0, 0.0, 0.0, 1.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+});
+
+g.test('size_zero_after_configure').
+desc(
+ `Ensure a validation error is raised after configure() if the size of the canvas becomes zero.`
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+combine('zeroDimension', ['width', 'height'])
+).
+fn((t) => {
+ const { canvasType, zeroDimension } = t.params;
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: t.device,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ canvas[zeroDimension] = 0;
+
+ // The size is incorrect, we should be getting an error texture and a validation error.
+ let currentTexture;
+ t.expectValidationError(() => {
+ currentTexture = ctx.getCurrentTexture();
+ });
+
+ t.expect(currentTexture[zeroDimension] === 0);
+
+ // Using the texture should produce a validation error.
+ t.expectValidationError(() => {
+ currentTexture.createView();
+ });
+});
+
+g.test('viewFormats').
+desc(
+ `Test the validation that viewFormats are compatible with the format (for all canvas format / view formats)`
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+combine('format', kCanvasTextureFormats).
+combine('viewFormatFeature', kFeaturesForFormats).
+beginSubcases().
+expand('viewFormat', ({ viewFormatFeature }) =>
+filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+)
+).
+beforeAllSubcases((t) => {
+ t.selectDeviceOrSkipTestCase([t.params.viewFormatFeature]);
+}).
+fn((t) => {
+ const { canvasType, format, viewFormat } = t.params;
+
+ t.skipIfTextureFormatNotSupported(viewFormat);
+
+ const canvas = createCanvas(t, canvasType, 1, 1);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ const compatible = viewCompatible(format, viewFormat);
+
+ // Test configure() produces an error if the formats aren't compatible.
+ t.expectValidationError(() => {
+ ctx.configure({
+ device: t.device,
+ format,
+ viewFormats: [viewFormat]
+ });
+ }, !compatible);
+
+ // Likewise for getCurrentTexture().
+ let currentTexture;
+ t.expectValidationError(() => {
+ currentTexture = ctx.getCurrentTexture();
+ }, !compatible);
+
+ // The returned texture is an error texture.
+ t.expectValidationError(() => {
+ currentTexture.createView();
+ }, !compatible);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/context_creation.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/context_creation.spec.js
new file mode 100644
index 0000000000..d165f7d03a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/context_creation.spec.js
@@ -0,0 +1,47 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for canvas context creation.
+
+Note there are no context creation attributes for WebGPU (as of this writing).
+Options are configured in configure() instead.
+`;import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+
+export const g = makeTestGroup(Fixture);
+
+g.test('return_type').
+desc(
+ `Test the return type of getContext for WebGPU.
+
+ TODO: Test OffscreenCanvas made from transferControlToOffscreen.`
+).
+params((u) =>
+u //
+.combine('offscreen', [false, true]).
+beginSubcases().
+combine('attributes', [undefined, {}])
+).
+fn((t) => {
+ let canvas;
+ if (t.params.offscreen) {
+ if (typeof OffscreenCanvas === 'undefined') {
+ // Skip if the current context doesn't have OffscreenCanvas (e.g. Node).
+ t.skip('OffscreenCanvas is not available in this context');
+ }
+
+ canvas = new OffscreenCanvas(10, 10);
+ } else {
+ if (typeof document === 'undefined') {
+ // Skip if there is no document (Workers, Node)
+ t.skip('DOM is not available to create canvas element');
+ }
+
+ canvas = document.createElement('canvas', t.params.attributes);
+ canvas.width = 10;
+ canvas.height = 10;
+ }
+
+ const ctx = canvas.getContext('webgpu');
+ t.expect(ctx instanceof GPUCanvasContext);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getCurrentTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getCurrentTexture.spec.js
new file mode 100644
index 0000000000..00e8c3ec24
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getCurrentTexture.spec.js
@@ -0,0 +1,383 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for GPUCanvasContext.getCurrentTexture.
+`;import { SkipTestCase } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { timeout } from '../../../common/util/timeout.js';
+import { assert, unreachable } from '../../../common/util/util.js';
+import { GPUTest } from '../../gpu_test.js';
+import { kAllCanvasTypes, createCanvas } from '../../util/create_elements.js';
+
+const kFormat = 'bgra8unorm';
+
+class GPUContextTest extends GPUTest {
+ initCanvasContext(canvasType = 'onscreen') {
+ const canvas = createCanvas(this, canvasType, 2, 2);
+ if (canvasType === 'onscreen') {
+ // To make sure onscreen canvas are visible
+ const onscreencanvas = canvas;
+ onscreencanvas.style.position = 'fixed';
+ onscreencanvas.style.top = '0';
+ onscreencanvas.style.left = '0';
+ // Set it to transparent so that if multiple canvas are created, they are still visible.
+ onscreencanvas.style.opacity = '50%';
+ document.body.appendChild(onscreencanvas);
+ this.trackForCleanup({
+ close() {
+ document.body.removeChild(onscreencanvas);
+ }
+ });
+ }
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: this.device,
+ format: kFormat,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+
+ return ctx;
+ }
+}
+
+export const g = makeTestGroup(GPUContextTest);
+
+g.test('configured').
+desc(
+ `Checks that calling getCurrentTexture requires the context to be configured first, and
+ that each call to configure causes getCurrentTexture to return a new texture.`
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes)
+).
+fn((t) => {
+ const canvas = createCanvas(t, t.params.canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ // Calling getCurrentTexture prior to configuration should throw an InvalidStateError exception.
+ t.shouldThrow('InvalidStateError', () => {
+ ctx.getCurrentTexture();
+ });
+
+ // Once the context has been configured getCurrentTexture can be called.
+ ctx.configure({
+ device: t.device,
+ format: kFormat
+ });
+
+ let prevTexture = ctx.getCurrentTexture();
+
+ // Calling configure again with different values will change the texture returned.
+ ctx.configure({
+ device: t.device,
+ format: 'bgra8unorm'
+ });
+
+ let currentTexture = ctx.getCurrentTexture();
+ t.expect(prevTexture !== currentTexture);
+ prevTexture = currentTexture;
+
+ // Calling configure again with the same values will still change the texture returned.
+ ctx.configure({
+ device: t.device,
+ format: 'bgra8unorm'
+ });
+
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(prevTexture !== currentTexture);
+ prevTexture = currentTexture;
+
+ // Calling getCurrentTexture after calling unconfigure should throw an InvalidStateError exception.
+ ctx.unconfigure();
+
+ t.shouldThrow('InvalidStateError', () => {
+ ctx.getCurrentTexture();
+ });
+});
+
+g.test('single_frames').
+desc(`Checks that the value of getCurrentTexture is consistent within a single frame.`).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes)
+).
+fn((t) => {
+ const ctx = t.initCanvasContext(t.params.canvasType);
+ const frameTexture = ctx.getCurrentTexture();
+
+ // Calling getCurrentTexture a second time returns the same texture.
+ t.expect(frameTexture === ctx.getCurrentTexture());
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: frameTexture.createView(),
+ clearValue: [1.0, 0.0, 0.0, 1.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ // Calling getCurrentTexture after performing some work on the texture returns the same texture.
+ t.expect(frameTexture === ctx.getCurrentTexture());
+
+ // Ensure that getCurrentTexture does not clear the texture.
+ t.expectSingleColor(frameTexture, frameTexture.format, {
+ size: [frameTexture.width, frameTexture.height, 1],
+ exp: { R: 1, G: 0, B: 0, A: 1 }
+ });
+
+ frameTexture.destroy();
+
+ // Calling getCurrentTexture after destroying the texture still returns the same texture.
+ t.expect(frameTexture === ctx.getCurrentTexture());
+});
+
+g.test('multiple_frames').
+desc(`Checks that the value of getCurrentTexture differs across multiple frames.`).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+beginSubcases().
+combine('clearTexture', [true, false])
+).
+beforeAllSubcases((t) => {
+ const { canvasType } = t.params;
+ if (canvasType === 'offscreen' && !('transferToImageBitmap' in OffscreenCanvas.prototype)) {
+ throw new SkipTestCase('transferToImageBitmap not supported');
+ }
+}).
+fn((t) => {
+ const { canvasType, clearTexture } = t.params;
+
+ return new Promise((resolve) => {
+ const ctx = t.initCanvasContext(canvasType);
+ let prevTexture;
+ let frameCount = 0;
+
+ function frameCheck() {
+ const currentTexture = ctx.getCurrentTexture();
+
+ if (prevTexture) {
+ // Ensure that each frame a new texture object is returned.
+ t.expect(currentTexture !== prevTexture);
+
+ // Ensure that texture contents are transparent black.
+ t.expectSingleColor(currentTexture, currentTexture.format, {
+ size: [currentTexture.width, currentTexture.height, 1],
+ exp: { R: 0, G: 0, B: 0, A: 0 }
+ });
+ }
+
+ if (clearTexture) {
+ // Clear the texture to test that texture contents don't carry over from frame to frame.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: currentTexture.createView(),
+ clearValue: [1.0, 0.0, 0.0, 1.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ prevTexture = currentTexture;
+
+ if (frameCount++ < 5) {
+ // Which method will be used to begin a new "frame"?
+ switch (canvasType) {
+ case 'onscreen':
+ requestAnimationFrame(frameCheck);
+ break;
+ case 'offscreen':{
+ ctx.canvas.transferToImageBitmap();
+ frameCheck();
+ break;
+ }
+ default:
+ unreachable();
+ }
+ } else {
+ resolve();
+ }
+ }
+
+ // 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);
+ });
+});
+
+g.test('resize').
+desc(`Checks the value of getCurrentTexture differs when the canvas is resized.`).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes)
+).
+fn((t) => {
+ const ctx = t.initCanvasContext(t.params.canvasType);
+ let prevTexture = ctx.getCurrentTexture();
+
+ // Trigger a resize by changing the width.
+ ctx.canvas.width = 4;
+
+ // When the canvas resizes the texture returned by getCurrentTexture should immediately begin
+ // returning a new texture matching the update dimensions.
+ let currentTexture = ctx.getCurrentTexture();
+ t.expect(prevTexture !== currentTexture);
+ t.expect(currentTexture.width === ctx.canvas.width);
+ t.expect(currentTexture.height === ctx.canvas.height);
+
+ // The width and height of the previous texture should remain unchanged.
+ t.expect(prevTexture.width === 2);
+ t.expect(prevTexture.height === 2);
+ prevTexture = currentTexture;
+
+ // Ensure that texture contents are transparent black.
+ t.expectSingleColor(currentTexture, currentTexture.format, {
+ size: [currentTexture.width, currentTexture.height, 1],
+ exp: { R: 0, G: 0, B: 0, A: 0 }
+ });
+
+ // Trigger a resize by changing the height.
+ ctx.canvas.height = 4;
+
+ // Check to ensure the texture is resized again.
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(prevTexture !== currentTexture);
+ t.expect(currentTexture.width === ctx.canvas.width);
+ 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, {
+ size: [currentTexture.width, currentTexture.height, 1],
+ 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);
+});
+
+g.test('expiry').
+desc(
+ `
+Test automatic WebGPU canvas texture expiry on all canvas types with the following requirements:
+- getCurrentTexture returns the same texture object until the next task:
+ - after previous frame update the rendering
+ - before current frame update the rendering
+ - in a microtask off the current frame task
+- getCurrentTexture returns a new texture object and the old texture object becomes invalid
+ as soon as possible after HTML update the rendering.
+
+TODO: test more canvas types, and ways to update the rendering
+- if on a different thread, expiry happens when the worker updates its rendering (worker "rPAF") OR transferToImageBitmap is called
+- [draw, transferControlToOffscreen, then canvas is displayed] on either {main thread, or transferred to worker}
+- [draw, canvas is displayed, then transferControlToOffscreen] on either {main thread, or transferred to worker}
+- reftests for the above 2 (what gets displayed when the canvas is displayed)
+- with canvas element added to DOM or not (applies to other canvas tests as well)
+ - canvas is added to DOM after being rendered
+ - canvas is already in DOM but becomes visible after being rendered
+ `
+).
+params((u) =>
+u //
+.combine('canvasType', kAllCanvasTypes).
+combine('prevFrameCallsite', ['runInNewCanvasFrame', 'requestAnimationFrame']).
+combine('getCurrentTextureAgain', [true, false])
+).
+fn((t) => {
+ const { canvasType, prevFrameCallsite, getCurrentTextureAgain } = t.params;
+ const ctx = t.initCanvasContext(t.params.canvasType);
+ // Create a bindGroupLayout to test invalid texture view usage later.
+ const bgl = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ texture: {}
+ }]
+
+ });
+
+ // The fn is called immediately after previous frame updating the rendering.
+ // Polyfill by calling the callback by setTimeout, in the requestAnimationFrame callback (for onscreen canvas)
+ // or after transferToImageBitmap (for offscreen canvas).
+ function runInNewCanvasFrame(fn) {
+ switch (canvasType) {
+ case 'onscreen':
+ requestAnimationFrame(() => timeout(fn));
+ break;
+ case 'offscreen':
+ // for offscreen canvas, after calling transferToImageBitmap, we are in a new frame immediately
+ ctx.canvas.transferToImageBitmap();
+ fn();
+ break;
+ default:
+ unreachable();
+ }
+ }
+
+ function checkGetCurrentTexture() {
+ // Call getCurrentTexture on previous frame.
+ const prevTexture = ctx.getCurrentTexture();
+
+ // Call getCurrentTexture immediately after the frame, the texture object should stay the same.
+ queueMicrotask(() => {
+ if (getCurrentTextureAgain) {
+ t.expect(prevTexture === ctx.getCurrentTexture());
+ }
+
+ // Call getCurrentTexture in a new frame.
+ // It should expire the previous texture object return a new texture object by the next frame by then.
+ // Call runInNewCanvasFrame in the micro task to make sure the new frame run after the getCurrentTexture in the micro task for offscreen canvas.
+ runInNewCanvasFrame(() => {
+ if (getCurrentTextureAgain) {
+ t.expect(prevTexture !== ctx.getCurrentTexture());
+ }
+
+ // Event when prevTexture expired, createView should still succeed anyway.
+ const prevTextureView = prevTexture.createView();
+ // Using the invalid view should fail if it expires.
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout: bgl,
+ entries: [{ binding: 0, resource: prevTextureView }]
+ });
+ });
+ });
+ });
+ }
+
+ switch (prevFrameCallsite) {
+ case 'runInNewCanvasFrame':
+ runInNewCanvasFrame(checkGetCurrentTexture);
+ break;
+ case 'requestAnimationFrame':
+ requestAnimationFrame(checkGetCurrentTexture);
+ break;
+ default:
+ break;
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getPreferredCanvasFormat.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getPreferredCanvasFormat.spec.js
new file mode 100644
index 0000000000..6c98193982
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/getPreferredCanvasFormat.spec.js
@@ -0,0 +1,19 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for navigator.gpu.getPreferredCanvasFormat.
+`;import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+
+export const g = makeTestGroup(Fixture);
+
+g.test('value').
+desc(
+ `
+ Ensure getPreferredCanvasFormat returns one of the valid values.
+ `
+).
+fn((t) => {
+ const preferredFormat = navigator.gpu.getPreferredCanvasFormat();
+ t.expect(preferredFormat === 'bgra8unorm' || preferredFormat === 'rgba8unorm');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.js
new file mode 100644
index 0000000000..05b422f8ea
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.js
@@ -0,0 +1,481 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests for readback from WebGPU Canvas.
+
+This includes testing that colorSpace makes it through from the WebGPU canvas
+to the form of copy (toDataURL, toBlob, ImageBitmap, drawImage)
+
+The color space support is tested by drawing the readback form of the WebGPU
+canvas into a 2D canvas of a different color space via drawImage (A). Another
+2D canvas is created with the same source data and color space as the WebGPU
+canvas and also drawn into another 2D canvas of a different color space (B).
+The contents of A and B should match.
+
+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 {
+ kCanvasAlphaModes,
+ kCanvasColorSpaces,
+ kCanvasTextureFormats } from
+'../../capability_info.js';
+import { GPUTest } from '../../gpu_test.js';
+import { checkElementsEqual } from '../../util/check_contents.js';
+import {
+ kAllCanvasTypes,
+
+ createCanvas,
+ createOnscreenCanvas } from
+'../../util/create_elements.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// We choose 0x66 as the value for each color and alpha channel
+// 0x66 / 0xff = 0.4
+// Given a pixel value of RGBA = (0x66, 0, 0, 0x66) in the source WebGPU canvas,
+// For alphaMode = opaque, the copy output should be RGBA = (0x66, 0, 0, 0xff)
+// For alphaMode = premultiplied, the copy output should be RGBA = (0xff, 0, 0, 0x66)
+const kPixelValue = 0x66;
+const kPixelValueFloat = 0x66 / 0xff; // 0.4
+
+// Use four pixels rectangle for the test:
+// blue: top-left;
+// green: top-right;
+// red: bottom-left;
+// yellow: bottom-right;
+const expect = {
+
+ 'opaque': new Uint8ClampedArray([
+ 0x00, 0x00, kPixelValue, 0xff, // blue
+ 0x00, kPixelValue, 0x00, 0xff, // green
+ kPixelValue, 0x00, 0x00, 0xff, // red
+ kPixelValue, kPixelValue, 0x00, 0xff // yellow
+ ]),
+
+ 'premultiplied': new Uint8ClampedArray([
+ 0x00, 0x00, 0xff, kPixelValue, // blue
+ 0x00, 0xff, 0x00, kPixelValue, // green
+ 0xff, 0x00, 0x00, kPixelValue, // red
+ 0xff, 0xff, 0x00, kPixelValue // yellow
+ ])
+};
+
+function initWebGPUCanvasContent(
+t,
+format,
+alphaMode,
+colorSpace,
+canvasType)
+{
+ const canvas = createCanvas(t, canvasType, 2, 2);
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ ctx.configure({
+ device: t.device,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ alphaMode,
+ colorSpace
+ });
+
+ const canvasTexture = ctx.getCurrentTexture();
+ const tempTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ const tempTextureView = tempTexture.createView();
+ const encoder = t.device.createCommandEncoder();
+
+ const clearOnePixel = (origin, color) => {
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ { view: tempTextureView, clearValue: color, loadOp: 'clear', storeOp: 'store' }]
+
+ });
+ pass.end();
+ encoder.copyTextureToTexture(
+ { texture: tempTexture },
+ { texture: canvasTexture, origin },
+ { width: 1, height: 1 }
+ );
+ };
+
+ clearOnePixel([0, 0], [0, 0, kPixelValueFloat, kPixelValueFloat]);
+ clearOnePixel([1, 0], [0, kPixelValueFloat, 0, kPixelValueFloat]);
+ clearOnePixel([0, 1], [kPixelValueFloat, 0, 0, kPixelValueFloat]);
+ clearOnePixel([1, 1], [kPixelValueFloat, kPixelValueFloat, 0, kPixelValueFloat]);
+
+ t.device.queue.submit([encoder.finish()]);
+ tempTexture.destroy();
+
+ return canvas;
+}
+
+function drawImageSourceIntoCanvas(
+t,
+image,
+colorSpace)
+{
+ const canvas = createOnscreenCanvas(t, 2, 2);
+ const ctx = canvas.getContext('2d', { colorSpace });
+ assert(ctx !== null);
+ ctx.drawImage(image, 0, 0);
+ return ctx;
+}
+
+function checkImageResultWithSameColorSpaceCanvas(
+t,
+image,
+sourceColorSpace,
+expect)
+{
+ const ctx = drawImageSourceIntoCanvas(t, image, sourceColorSpace);
+ readPixelsFrom2DCanvasAndCompare(t, ctx, expect);
+}
+
+function checkImageResultWithDifferentColorSpaceCanvas(
+t,
+image,
+sourceColorSpace,
+sourceData)
+{
+ const destinationColorSpace = sourceColorSpace === 'srgb' ? 'display-p3' : 'srgb';
+
+ // 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 = 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);
+}
+
+function checkImageResult(
+t,
+image,
+sourceColorSpace,
+expect)
+{
+ checkImageResultWithSameColorSpaceCanvas(t, image, sourceColorSpace, expect);
+ checkImageResultWithDifferentColorSpaceCanvas(t, image, sourceColorSpace, expect);
+}
+
+function readPixelsFrom2DCanvasAndCompare(
+t,
+ctx,
+expect)
+{
+ const actual = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
+
+ t.expectOK(checkElementsEqual(actual, expect));
+}
+
+g.test('onscreenCanvas,snapshot').
+desc(
+ `
+ Ensure snapshot of canvas with WebGPU context is correct with
+ - various WebGPU canvas texture formats
+ - WebGPU canvas alpha mode = {"opaque", "premultiplied"}
+ - colorSpace = {"srgb", "display-p3"}
+ - snapshot methods = {convertToBlob, transferToImageBitmap, createImageBitmap}
+
+ TODO: Snapshot canvas to jpeg, webp and other mime type and
+ different quality. Maybe we should test them in reftest.
+ `
+).
+params((u) =>
+u //
+.combine('format', kCanvasTextureFormats).
+combine('alphaMode', kCanvasAlphaModes).
+combine('colorSpace', kCanvasColorSpaces).
+combine('snapshotType', ['toDataURL', 'toBlob', 'imageBitmap'])
+).
+fn(async (t) => {
+ const canvas = initWebGPUCanvasContent(
+ t,
+ t.params.format,
+ t.params.alphaMode,
+ t.params.colorSpace,
+ 'onscreen'
+ );
+
+ let snapshot;
+ switch (t.params.snapshotType) {
+ case 'toDataURL':{
+ const url = canvas.toDataURL();
+ const img = new Image(canvas.width, canvas.height);
+ img.src = url;
+ await raceWithRejectOnTimeout(img.decode(), 5000, 'load image timeout');
+ snapshot = img;
+ break;
+ }
+ case 'toBlob':{
+ const blobFromCanvas = new Promise((resolve) => {
+ canvas.toBlob((blob) => resolve(blob));
+ });
+ const blob = await blobFromCanvas;
+ const url = URL.createObjectURL(blob);
+ const img = new Image(canvas.width, canvas.height);
+ img.src = url;
+ await raceWithRejectOnTimeout(img.decode(), 5000, 'load image timeout');
+ snapshot = img;
+ break;
+ }
+ case 'imageBitmap':{
+ snapshot = await createImageBitmap(canvas);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ checkImageResult(t, snapshot, t.params.colorSpace, expect[t.params.alphaMode]);
+});
+
+g.test('offscreenCanvas,snapshot').
+desc(
+ `
+ Ensure snapshot of offscreenCanvas with WebGPU context is correct with
+ - various WebGPU canvas texture formats
+ - WebGPU canvas alpha mode = {"opaque", "premultiplied"}
+ - colorSpace = {"srgb", "display-p3"}
+ - snapshot methods = {convertToBlob, transferToImageBitmap, createImageBitmap}
+
+ TODO: Snapshot offscreenCanvas to jpeg, webp and other mime type and
+ different quality. Maybe we should test them in reftest.
+ `
+).
+params((u) =>
+u //
+.combine('format', kCanvasTextureFormats).
+combine('alphaMode', kCanvasAlphaModes).
+combine('colorSpace', kCanvasColorSpaces).
+combine('snapshotType', ['convertToBlob', 'transferToImageBitmap', 'imageBitmap'])
+).
+fn(async (t) => {
+ const offscreenCanvas = initWebGPUCanvasContent(
+ t,
+ t.params.format,
+ t.params.alphaMode,
+ t.params.colorSpace,
+ 'offscreen'
+ );
+
+ let snapshot;
+ switch (t.params.snapshotType) {
+ case 'convertToBlob':{
+ if (typeof offscreenCanvas.convertToBlob === 'undefined') {
+ t.skip("Browser doesn't support OffscreenCanvas.convertToBlob");
+ 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;
+ break;
+ }
+ case 'transferToImageBitmap':{
+ if (typeof offscreenCanvas.transferToImageBitmap === 'undefined') {
+ t.skip("Browser doesn't support OffscreenCanvas.transferToImageBitmap");
+ return;
+ }
+ snapshot = offscreenCanvas.transferToImageBitmap();
+ break;
+ }
+ case 'imageBitmap':{
+ snapshot = await createImageBitmap(offscreenCanvas);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ checkImageResult(t, snapshot, t.params.colorSpace, expect[t.params.alphaMode]);
+});
+
+g.test('onscreenCanvas,uploadToWebGL').
+desc(
+ `
+ Ensure upload WebGPU context canvas to webgl texture is correct with
+ - various WebGPU canvas texture formats
+ - WebGPU canvas alpha mode = {"opaque", "premultiplied"}
+ - upload methods = {texImage2D, texSubImage2D}
+ `
+).
+params((u) =>
+u //
+.combine('format', kCanvasTextureFormats).
+combine('alphaMode', kCanvasAlphaModes).
+combine('webgl', ['webgl', 'webgl2']).
+combine('upload', ['texImage2D', 'texSubImage2D'])
+).
+fn((t) => {
+ const { format, webgl, upload } = t.params;
+ const canvas = initWebGPUCanvasContent(t, format, t.params.alphaMode, 'srgb', 'onscreen');
+
+ const expectCanvas = createOnscreenCanvas(t, canvas.width, canvas.height);
+ const gl = expectCanvas.getContext(webgl);
+ if (gl === null) {
+ return;
+ }
+
+ const texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ switch (upload) {
+ case 'texImage2D':{
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
+ break;
+ }
+ case 'texSubImage2D':{
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ canvas.width,
+ canvas.height,
+ 0,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ null
+ );
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ const fb = gl.createFramebuffer();
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+
+ const pixels = new Uint8Array(canvas.width * canvas.height * 4);
+ gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ const actual = new Uint8ClampedArray(pixels);
+
+ t.expectOK(checkElementsEqual(actual, expect[t.params.alphaMode]));
+});
+
+g.test('drawTo2DCanvas').
+desc(
+ `
+ Ensure draw WebGPU context canvas to 2d context canvas/offscreenCanvas is correct with
+ - various WebGPU canvas texture formats
+ - WebGPU canvas alpha mode = {"opaque", "premultiplied"}
+ - colorSpace = {"srgb", "display-p3"}
+ - WebGPU canvas type = {"onscreen", "offscreen"}
+ - 2d canvas type = {"onscreen", "offscreen"}
+ `
+).
+params((u) =>
+u //
+.combine('format', kCanvasTextureFormats).
+combine('alphaMode', kCanvasAlphaModes).
+combine('colorSpace', kCanvasColorSpaces).
+combine('webgpuCanvasType', kAllCanvasTypes).
+combine('canvas2DType', kAllCanvasTypes)
+).
+fn((t) => {
+ const { format, webgpuCanvasType, alphaMode, colorSpace, canvas2DType } = t.params;
+
+ const canvas = initWebGPUCanvasContent(t, format, alphaMode, colorSpace, webgpuCanvasType);
+
+ const expectCanvas = createCanvas(t, canvas2DType, canvas.width, canvas.height);
+ const ctx = expectCanvas.getContext('2d');
+ if (ctx === null) {
+ t.skip(canvas2DType + ' canvas cannot get 2d context');
+ return;
+ }
+
+ ctx.drawImage(canvas, 0, 0);
+ readPixelsFrom2DCanvasAndCompare(t, ctx, expect[t.params.alphaMode]);
+});
+
+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`
+).
+fn((t) => {
+ const canvas = createCanvas(t, 'offscreen', 2, 3);
+ 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);
+
+ const readbackCanvas = document.createElement('canvas');
+ readbackCanvas.width = canvas.width;
+ readbackCanvas.height = canvas.height;
+ const readbackContext = readbackCanvas.getContext('2d', {
+ alpha: true
+ });
+ if (readbackContext === null) {
+ t.skip('Cannot get a 2D canvas context');
+ return;
+ }
+
+ // 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);
+ for (let i = 0; i < expected.byteLength; i += 4) {
+ expected[i + 0] = 0;
+ expected[i + 1] = 0;
+ expected[i + 2] = 0;
+ expected[i + 3] = 255;
+ }
+
+ readbackContext.drawImage(ib, 0, 0);
+ readPixelsFrom2DCanvasAndCompare(t, readbackContext, expected);
+});
+
+g.test('transferToImageBitmap_zero_size').
+desc(
+ `Regression test for a crash when calling transferImageBitmap on an unconfigured. Case where the canvas is empty.
+
+ TODO: Spec and expect a particular Exception type here.`
+).
+params((u) => u.combine('configure', [true, false])).
+fn((t) => {
+ const { configure } = t.params;
+ const canvas = createCanvas(t, 'offscreen', 0, 1);
+ const ctx = canvas.getContext('webgpu');
+
+ if (configure) {
+ t.expectValidationError(() => ctx.configure({ device: t.device, format: 'bgra8unorm' }));
+ }
+
+ // Transferring would give an empty ImageBitmap which is not possible, so an Exception is thrown.
+ t.shouldThrow(true, () => {
+ canvas.transferToImageBitmap();
+ });
+});
+
+g.test('transferToImageBitmap_huge_size').
+desc(`Regression test for a crash when calling transferImageBitmap on a HUGE canvas.`).
+fn((t) => {
+ const canvas = createCanvas(t, 'offscreen', 1000000, 1000000);
+ canvas.getContext('webgpu');
+
+ // Transferring to such a HUGE image bitmap would not be possible, so an Exception is thrown.
+ t.shouldThrow(true, () => {
+ canvas.transferToImageBitmap();
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageBitmap.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageBitmap.spec.js
new file mode 100644
index 0000000000..e5340e1872
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageBitmap.spec.js
@@ -0,0 +1,543 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyExternalImageToTexture from ImageBitmaps created from various sources.
+
+TODO: Test ImageBitmap generated from all possible ImageBitmapSource, relevant ImageBitmapOptions
+ (https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#images-2)
+ and various source filetypes and metadata (weird dimensions, EXIF orientations, video rotations
+ and visible/crop rectangles, etc. (In theory these things are handled inside createImageBitmap,
+ but in theory could affect the internal representation of the ImageBitmap.)
+
+TODO: Test zero-sized copies from all sources (just make sure params cover it) (e.g. 0x0, 0x4, 4x0).
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js';
+import { CopyToTextureUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
+
+import { kTestColorsAll, kTestColorsOpaque, makeTestColorsTexelView } from './util.js';
+
+export const g = makeTestGroup(CopyToTextureUtils);
+
+g.test('from_ImageData').
+desc(
+ `
+ Test ImageBitmap generated from ImageData can be copied to WebGPU
+ texture correctly. These imageBitmaps are highly possible living
+ in CPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White, SemitransparentWhite].
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the ImageBitmap contents.
+
+ Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid dstFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcFlipYInCopy' in cases)
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('alpha', ['none', 'premultiply']).
+combine('orientation', ['none', 'flipY']).
+combine('colorSpaceConversion', ['none', 'default']).
+combine('srcFlipYInCopy', [true, false]).
+combine('dstFormat', kValidTextureFormatsForCopyE2T).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15, 255, 256]).
+combine('height', [1, 2, 4, 15, 255, 256])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstFormat);
+}).
+fn(async (t) => {
+ const {
+ width,
+ height,
+ alpha,
+ orientation,
+ colorSpaceConversion,
+ dstFormat,
+ dstPremultiplied,
+ srcFlipYInCopy
+ } = t.params;
+
+ const testColors = kTestColorsAll;
+
+ // Generate correct expected values
+ const texelViewSource = makeTestColorsTexelView({
+ testColors,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width,
+ height,
+ flipY: false,
+ premultiplied: false
+ });
+ const imageData = new ImageData(width, height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin: [0, 0],
+ subrectSize: { width, height }
+ });
+
+ const imageBitmap = await createImageBitmap(imageData, {
+ premultiplyAlpha: alpha,
+ imageOrientation: orientation,
+ colorSpaceConversion
+ });
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const expFormat = kTextureFormatInfo[dstFormat].baseFormat ?? dstFormat;
+ const flipSrcBeforeCopy = orientation === 'flipY';
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin: [0, 0],
+ srcSize: [width, height],
+ dstOrigin: [0, 0],
+ dstSize: [width, height],
+ subRectSize: [width, height],
+ format: expFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy: srcFlipYInCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcFlipYInCopy },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ { width, height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+});
+
+g.test('from_canvas').
+desc(
+ `
+ Test ImageBitmap generated from canvas/offscreenCanvas can be copied to WebGPU
+ texture correctly. These imageBitmaps are highly possible living in GPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White].
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the ImageBitmap contents.
+
+ Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid 2D canvas
+ - Valid dstFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcFlipYInCopy' in cases)
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('orientation', ['none', 'flipY']).
+combine('colorSpaceConversion', ['none', 'default']).
+combine('srcFlipYInCopy', [true, false]).
+combine('dstFormat', kValidTextureFormatsForCopyE2T).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15, 255, 256]).
+combine('height', [1, 2, 4, 15, 255, 256])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstFormat);
+}).
+fn(async (t) => {
+ const {
+ width,
+ height,
+ orientation,
+ colorSpaceConversion,
+ dstFormat,
+ dstPremultiplied,
+ srcFlipYInCopy
+ } = t.params;
+
+ // CTS sometimes runs on worker threads, where document is not available.
+ // In this case, OffscreenCanvas can be used instead of <canvas>.
+ // But some browsers don't support OffscreenCanvas, and some don't
+ // support '2d' contexts on OffscreenCanvas.
+ // In this situation, the case will be skipped.
+ let imageCanvas;
+ if (typeof document !== 'undefined') {
+ imageCanvas = document.createElement('canvas');
+ imageCanvas.width = width;
+ imageCanvas.height = height;
+ } else if (typeof OffscreenCanvas === 'undefined') {
+ t.skip('OffscreenCanvas is not supported');
+ return;
+ } else {
+ imageCanvas = new OffscreenCanvas(width, height);
+ }
+ const imageCanvasContext = imageCanvas.getContext('2d');
+ if (imageCanvasContext === null) {
+ t.skip('OffscreenCanvas "2d" context not available');
+ return;
+ }
+
+ // Generate non-transparent pixel data to avoid canvas
+ // different opt behaviour on putImageData()
+ // from browsers.
+ const texelViewSource = makeTestColorsTexelView({
+ testColors: kTestColorsOpaque,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width,
+ height,
+ flipY: false,
+ premultiplied: false
+ });
+ // Generate correct expected values
+ const imageData = new ImageData(width, height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin: [0, 0],
+ subrectSize: { width, height }
+ });
+
+ // Use putImageData to prevent color space conversion.
+ imageCanvasContext.putImageData(imageData, 0, 0);
+
+ // MAINTENANCE_TODO: Workaround for @types/offscreencanvas missing an overload of
+ // `createImageBitmap` that takes `ImageBitmapOptions`.
+ const imageBitmap = await createImageBitmap(imageCanvas, {
+ premultiplyAlpha: 'premultiply',
+ imageOrientation: orientation,
+ colorSpaceConversion
+ });
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const expFormat = kTextureFormatInfo[dstFormat].baseFormat ?? dstFormat;
+ const flipSrcBeforeCopy = orientation === 'flipY';
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin: [0, 0],
+ srcSize: [width, height],
+ dstOrigin: [0, 0],
+ dstSize: [width, height],
+ subRectSize: [width, height],
+ format: expFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy: srcFlipYInCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcFlipYInCopy },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ { width, height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+});
+
+g.test('copy_subrect_from_ImageData').
+desc(
+ `
+ Test ImageBitmap generated from ImageData can be copied to WebGPU
+ texture correctly. These imageBitmaps are highly possible living in CPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White].
+
+ Then call copyExternalImageToTexture() to do a subrect copy, based on a predefined copy
+ rect info list, to the 0 mipLevel of dst texture, and read the contents out to compare
+ with the ImageBitmap contents.
+
+ Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped, and origin is top-left consistantly.
+
+ The tests covers:
+ - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
+ - Valid dstFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcFlipYInCopy' in cases)
+ - Valid subrect copies.
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('alpha', ['none', 'premultiply']).
+combine('orientation', ['none', 'flipY']).
+combine('colorSpaceConversion', ['none', 'default']).
+combine('srcFlipYInCopy', [true, false]).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('copySubRectInfo', kCopySubrectInfo)
+).
+fn(async (t) => {
+ const {
+ copySubRectInfo,
+ alpha,
+ orientation,
+ colorSpaceConversion,
+ dstPremultiplied,
+ srcFlipYInCopy
+ } = t.params;
+
+ const testColors = kTestColorsAll;
+ const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
+ const kColorFormat = 'rgba8unorm';
+
+ // Generate correct expected values
+ const texelViewSource = makeTestColorsTexelView({
+ testColors,
+ format: kColorFormat, // ImageData is always in rgba8unorm format.
+ width: srcSize.width,
+ height: srcSize.height,
+ flipY: false,
+ premultiplied: false
+ });
+ const imageData = new ImageData(srcSize.width, srcSize.height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: srcSize.width * 4,
+ rowsPerImage: srcSize.height,
+ subrectOrigin: [0, 0],
+ subrectSize: srcSize
+ });
+
+ const imageBitmap = await createImageBitmap(imageData, {
+ premultiplyAlpha: alpha,
+ imageOrientation: orientation,
+ colorSpaceConversion
+ });
+
+ const dst = t.device.createTexture({
+ size: dstSize,
+ format: kColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const flipSrcBeforeCopy = orientation === 'flipY';
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin,
+ srcSize,
+ dstOrigin,
+ dstSize,
+ subRectSize: copyExtent,
+ format: kColorFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy: srcFlipYInCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: srcOrigin, flipY: srcFlipYInCopy },
+ {
+ texture: dst,
+ origin: dstOrigin,
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ copyExtent,
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+});
+
+g.test('copy_subrect_from_2D_Canvas').
+desc(
+ `
+ Test ImageBitmap generated from canvas/offscreenCanvas can be copied to WebGPU
+ texture correctly. These imageBitmaps are highly possible living in GPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White].
+
+ Then call copyExternalImageToTexture() to do a subrect copy, based on a predefined copy
+ rect info list, to the 0 mipLevel of dst texture, and read the contents out to compare
+ with the ImageBitmap contents.
+
+ Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped, and origin is top-left consistantly.
+
+ The tests covers:
+ - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
+ - Valid dstFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcFlipYInCopy' in cases)
+ - Valid subrect copies.
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('orientation', ['none', 'flipY']).
+combine('colorSpaceConversion', ['none', 'default']).
+combine('srcFlipYInCopy', [true, false]).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('copySubRectInfo', kCopySubrectInfo)
+).
+fn(async (t) => {
+ const { copySubRectInfo, orientation, colorSpaceConversion, dstPremultiplied, srcFlipYInCopy } =
+ t.params;
+
+ const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
+ const kColorFormat = 'rgba8unorm';
+
+ // CTS sometimes runs on worker threads, where document is not available.
+ // In this case, OffscreenCanvas can be used instead of <canvas>.
+ // But some browsers don't support OffscreenCanvas, and some don't
+ // support '2d' contexts on OffscreenCanvas.
+ // In this situation, the case will be skipped.
+ let imageCanvas;
+ if (typeof document !== 'undefined') {
+ imageCanvas = document.createElement('canvas');
+ imageCanvas.width = srcSize.width;
+ imageCanvas.height = srcSize.height;
+ } else if (typeof OffscreenCanvas === 'undefined') {
+ t.skip('OffscreenCanvas is not supported');
+ return;
+ } else {
+ imageCanvas = new OffscreenCanvas(srcSize.width, srcSize.height);
+ }
+ const imageCanvasContext = imageCanvas.getContext('2d');
+ if (imageCanvasContext === null) {
+ t.skip('OffscreenCanvas "2d" context not available');
+ return;
+ }
+
+ // Generate non-transparent pixel data to avoid canvas
+ // different opt behaviour on putImageData()
+ // from browsers.
+ const texelViewSource = makeTestColorsTexelView({
+ testColors: kTestColorsOpaque,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width: srcSize.width,
+ height: srcSize.height,
+ flipY: false,
+ premultiplied: false
+ });
+ // Generate correct expected values
+ const imageData = new ImageData(srcSize.width, srcSize.height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: srcSize.width * 4,
+ rowsPerImage: srcSize.height,
+ subrectOrigin: [0, 0],
+ subrectSize: srcSize
+ });
+
+ // Use putImageData to prevent color space conversion.
+ imageCanvasContext.putImageData(imageData, 0, 0);
+
+ // MAINTENANCE_TODO: Workaround for @types/offscreencanvas missing an overload of
+ // `createImageBitmap` that takes `ImageBitmapOptions`.
+ const imageBitmap = await createImageBitmap(imageCanvas, {
+ premultiplyAlpha: 'premultiply',
+ imageOrientation: orientation,
+ colorSpaceConversion
+ });
+
+ const dst = t.device.createTexture({
+ size: dstSize,
+ format: kColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const flipSrcBeforeCopy = orientation === 'flipY';
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin,
+ srcSize,
+ dstOrigin,
+ dstSize,
+ subRectSize: copyExtent,
+ format: kColorFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy: srcFlipYInCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: srcOrigin, flipY: srcFlipYInCopy },
+ {
+ texture: dst,
+ origin: dstOrigin,
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ copyExtent,
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageData.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageData.spec.js
new file mode 100644
index 0000000000..b285afa6cc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/ImageData.spec.js
@@ -0,0 +1,221 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyExternalImageToTexture from ImageData source.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js';
+import { CopyToTextureUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
+
+import { kTestColorsAll, makeTestColorsTexelView } from './util.js';
+
+export const g = makeTestGroup(CopyToTextureUtils);
+
+g.test('from_ImageData').
+desc(
+ `
+ Test ImageData can be copied to WebGPU
+ texture correctly. These imageDatas are highly possible living
+ in CPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White, SemitransparentWhite].
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the ImageData contents.
+
+ Expect alpha to get premultiplied in the copy if, and only if, 'premultipliedAlpha'
+ in 'GPUImageCopyTextureTagged' is set to 'true'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('srcDoFlipYDuringCopy', [true, false]).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15, 255, 256]).
+combine('height', [1, 2, 4, 15, 255, 256])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+}).
+fn((t) => {
+ const { width, height, dstColorFormat, dstPremultiplied, srcDoFlipYDuringCopy } = t.params;
+
+ const testColors = kTestColorsAll;
+
+ // Generate correct expected values
+ const texelViewSource = makeTestColorsTexelView({
+ testColors,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width,
+ height,
+ flipY: false,
+ premultiplied: false
+ });
+ const imageData = new ImageData(width, height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin: [0, 0],
+ subrectSize: { width, height }
+ });
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const expFormat = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat;
+ const flipSrcBeforeCopy = false;
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin: [0, 0],
+ srcSize: [width, height],
+ dstOrigin: [0, 0],
+ dstSize: [width, height],
+ subRectSize: [width, height],
+ format: expFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ {
+ source: imageData,
+ origin: { x: 0, y: 0 },
+ flipY: srcDoFlipYDuringCopy
+ },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ { width, height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+});
+
+g.test('copy_subrect_from_ImageData').
+desc(
+ `
+ Test ImageData can be copied to WebGPU
+ texture correctly. These imageDatas are highly possible living in CPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White].
+
+ Then call copyExternalImageToTexture() to do a subrect copy, based on a predefined copy
+ rect info list, to the 0 mipLevel of dst texture, and read the contents out to compare
+ with the ImageBitmap contents.
+
+ Expect alpha to get premultiplied in the copy if, and only if, 'premultipliedAlpha'
+ in 'GPUImageCopyTextureTagged' is set to 'true'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped, and origin is top-left consistantly.
+
+ The tests covers:
+ - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+ - Valid subrect copies.
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('srcDoFlipYDuringCopy', [true, false]).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('copySubRectInfo', kCopySubrectInfo)
+).
+fn((t) => {
+ const { copySubRectInfo, dstPremultiplied, srcDoFlipYDuringCopy } = t.params;
+
+ const testColors = kTestColorsAll;
+ const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
+ const kColorFormat = 'rgba8unorm';
+
+ // Generate correct expected values
+ const texelViewSource = makeTestColorsTexelView({
+ testColors,
+ format: kColorFormat, // ImageData is always in rgba8unorm format.
+ width: srcSize.width,
+ height: srcSize.height,
+ flipY: false,
+ premultiplied: false
+ });
+ const imageData = new ImageData(srcSize.width, srcSize.height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: srcSize.width * 4,
+ rowsPerImage: srcSize.height,
+ subrectOrigin: [0, 0],
+ subrectSize: srcSize
+ });
+
+ const dst = t.device.createTexture({
+ size: dstSize,
+ format: kColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const flipSrcBeforeCopy = false;
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin,
+ srcSize,
+ dstOrigin,
+ dstSize,
+ subRectSize: copyExtent,
+ format: kColorFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ {
+ source: imageData,
+ origin: srcOrigin,
+ flipY: srcDoFlipYDuringCopy
+ },
+ {
+ texture: dst,
+ origin: dstOrigin,
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ copyExtent,
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/canvas.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/canvas.spec.js
new file mode 100644
index 0000000000..a8176f0613
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/canvas.spec.js
@@ -0,0 +1,841 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyToTexture with HTMLCanvasElement and OffscreenCanvas sources.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { skipTestCase } from '../../../common/util/util.js';
+import { kCanvasAlphaModes } from '../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kValidTextureFormatsForCopyE2T } from
+
+'../../format_info.js';
+import { CopyToTextureUtils } from '../../util/copy_to_texture.js';
+import { kAllCanvasTypes, createCanvas } from '../../util/create_elements.js';
+
+
+class F extends CopyToTextureUtils {
+ init2DCanvasContentWithColorSpace({
+ width,
+ height,
+ colorSpace
+
+
+
+
+ })
+
+
+ {
+ const canvas = createCanvas(this, 'onscreen', width, height);
+
+ let canvasContext = null;
+ canvasContext = canvas.getContext('2d', { colorSpace });
+
+ if (canvasContext === null) {
+ this.skip('onscreen canvas 2d context not available');
+ }
+
+ if (
+ typeof canvasContext.getContextAttributes === 'undefined' ||
+ typeof canvasContext.getContextAttributes().colorSpace === 'undefined')
+ {
+ this.skip('color space attr is not supported for canvas 2d context');
+ }
+
+ const SOURCE_PIXEL_BYTES = 4;
+ const imagePixels = new Uint8ClampedArray(SOURCE_PIXEL_BYTES * width * height);
+
+ const rectWidth = Math.floor(width / 2);
+ const rectHeight = Math.floor(height / 2);
+
+ const alphaValue = 153;
+
+ let pixelStartPos = 0;
+ // Red;
+ for (let i = 0; i < rectHeight; ++i) {
+ for (let j = 0; j < rectWidth; ++j) {
+ pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
+ imagePixels[pixelStartPos] = 255;
+ imagePixels[pixelStartPos + 1] = 0;
+ imagePixels[pixelStartPos + 2] = 0;
+ imagePixels[pixelStartPos + 3] = alphaValue;
+ }
+ }
+
+ // Lime;
+ for (let i = 0; i < rectHeight; ++i) {
+ for (let j = rectWidth; j < width; ++j) {
+ pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
+ imagePixels[pixelStartPos] = 0;
+ imagePixels[pixelStartPos + 1] = 255;
+ imagePixels[pixelStartPos + 2] = 0;
+ imagePixels[pixelStartPos + 3] = alphaValue;
+ }
+ }
+
+ // Blue
+ for (let i = rectHeight; i < height; ++i) {
+ for (let j = 0; j < rectWidth; ++j) {
+ pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
+ imagePixels[pixelStartPos] = 0;
+ imagePixels[pixelStartPos + 1] = 0;
+ imagePixels[pixelStartPos + 2] = 255;
+ imagePixels[pixelStartPos + 3] = alphaValue;
+ }
+ }
+
+ // Fuchsia
+ for (let i = rectHeight; i < height; ++i) {
+ for (let j = rectWidth; j < width; ++j) {
+ pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
+ imagePixels[pixelStartPos] = 255;
+ imagePixels[pixelStartPos + 1] = 0;
+ imagePixels[pixelStartPos + 2] = 255;
+ imagePixels[pixelStartPos + 3] = alphaValue;
+ }
+ }
+
+ const imageData = new ImageData(imagePixels, width, height, { colorSpace });
+ // MAINTENANCE_TODO: Remove as any when tsc support imageData.colorSpace
+
+ if (typeof imageData.colorSpace === 'undefined') {
+ this.skip('color space attr is not supported for ImageData');
+ }
+
+ const ctx = canvasContext;
+ ctx.putImageData(imageData, 0, 0);
+
+ return {
+ canvas,
+ expectedSourceData: this.getExpectedReadbackFor2DCanvas(canvasContext, width, height)
+ };
+ }
+
+ // MAINTENANCE_TODO: Cache the generated canvas to avoid duplicated initialization.
+ init2DCanvasContent({
+ canvasType,
+ width,
+ height
+
+
+
+
+ })
+
+
+ {
+ const canvas = createCanvas(this, canvasType, width, height);
+
+ let canvasContext = null;
+ canvasContext = canvas.getContext('2d');
+
+ if (canvasContext === null) {
+ this.skip(canvasType + ' canvas 2d context not available');
+ }
+
+ const ctx = canvasContext;
+ this.paint2DCanvas(ctx, width, height, 0.6);
+
+ return {
+ canvas,
+ expectedSourceData: this.getExpectedReadbackFor2DCanvas(canvasContext, width, height)
+ };
+ }
+
+ paint2DCanvas(
+ ctx,
+ width,
+ height,
+ alphaValue)
+ {
+ const rectWidth = Math.floor(width / 2);
+ const rectHeight = Math.floor(height / 2);
+
+ // Red
+ ctx.fillStyle = `rgba(255, 0, 0, ${alphaValue})`;
+ ctx.fillRect(0, 0, rectWidth, rectHeight);
+ // Lime
+ ctx.fillStyle = `rgba(0, 255, 0, ${alphaValue})`;
+ ctx.fillRect(rectWidth, 0, width - rectWidth, rectHeight);
+ // Blue
+ ctx.fillStyle = `rgba(0, 0, 255, ${alphaValue})`;
+ ctx.fillRect(0, rectHeight, rectWidth, height - rectHeight);
+ // Fuchsia
+ ctx.fillStyle = `rgba(255, 0, 255, ${alphaValue})`;
+ ctx.fillRect(rectWidth, rectHeight, width - rectWidth, height - rectHeight);
+ }
+
+ // MAINTENANCE_TODO: Cache the generated canvas to avoid duplicated initialization.
+ initGLCanvasContent({
+ canvasType,
+ contextName,
+ width,
+ height,
+ premultiplied
+
+
+
+
+
+
+ })
+
+
+ {
+ const canvas = createCanvas(this, canvasType, width, height);
+
+ // MAINTENANCE_TODO: Workaround for @types/offscreencanvas missing an overload of
+ // `OffscreenCanvas.getContext` that takes `string` or a union of context types.
+ const gl = canvas.getContext(contextName, {
+ premultipliedAlpha: premultiplied
+ });
+
+ if (gl === null) {
+ this.skip(canvasType + ' canvas ' + contextName + ' context not available');
+ }
+ this.trackForCleanup(gl);
+
+ const rectWidth = Math.floor(width / 2);
+ const rectHeight = Math.floor(height / 2);
+
+ const alphaValue = 0.6;
+ const colorValue = premultiplied ? alphaValue : 1.0;
+
+ // For webgl/webgl2 context canvas, if the context created with premultipliedAlpha attributes,
+ // it means that the value in drawing buffer is premultiplied or not. So we should set
+ // premultipliedAlpha value for premultipliedAlpha true gl context and unpremultipliedAlpha value
+ // for the premultipliedAlpha false gl context.
+ gl.enable(gl.SCISSOR_TEST);
+ gl.scissor(0, 0, rectWidth, rectHeight);
+ gl.clearColor(colorValue, 0.0, 0.0, alphaValue);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.scissor(rectWidth, 0, width - rectWidth, rectHeight);
+ gl.clearColor(0.0, colorValue, 0.0, alphaValue);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.scissor(0, rectHeight, rectWidth, height - rectHeight);
+ gl.clearColor(0.0, 0.0, colorValue, alphaValue);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.scissor(rectWidth, rectHeight, width - rectWidth, height - rectHeight);
+ gl.clearColor(colorValue, colorValue, colorValue, alphaValue);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ return {
+ canvas,
+ expectedSourceData: this.getExpectedReadbackForWebGLCanvas(gl, width, height)
+ };
+ }
+
+ getDataToInitSourceWebGPUCanvas(
+ width,
+ height,
+ alphaMode)
+ {
+ const rectWidth = Math.floor(width / 2);
+ const rectHeight = Math.floor(height / 2);
+
+ const alphaValue = 153;
+ // Always output [153, 153, 153, 153]. When the alphaMode is...
+ // - premultiplied: the readback is CSS `rgba(255, 255, 255, 60%)`.
+ // - opaque: the readback is CSS `rgba(153, 153, 153, 100%)`.
+ // getExpectedReadbackForWebGPUCanvas matches this.
+ const colorValue = alphaValue;
+
+ // BGRA8Unorm texture
+ const initialData = new Uint8ClampedArray(4 * width * height);
+ const maxRectHeightIndex = width * rectHeight;
+ for (let pixelIndex = 0; pixelIndex < initialData.length / 4; ++pixelIndex) {
+ const index = pixelIndex * 4;
+
+ // Top-half two rectangles
+ if (pixelIndex < maxRectHeightIndex) {
+ // top-left side rectangle
+ if (pixelIndex % width < rectWidth) {
+ // top-left side rectangle
+ initialData[index] = colorValue;
+ initialData[index + 1] = 0;
+ initialData[index + 2] = 0;
+ initialData[index + 3] = alphaValue;
+ } else {
+ // top-right side rectangle
+ initialData[index] = 0;
+ initialData[index + 1] = colorValue;
+ initialData[index + 2] = 0;
+ initialData[index + 3] = alphaValue;
+ }
+ } else {
+ // Bottom-half two rectangles
+ // bottom-left side rectangle
+ if (pixelIndex % width < rectWidth) {
+ initialData[index] = 0;
+ initialData[index + 1] = 0;
+ initialData[index + 2] = colorValue;
+ initialData[index + 3] = alphaValue;
+ } else {
+ // bottom-right side rectangle
+ initialData[index] = colorValue;
+ initialData[index + 1] = colorValue;
+ initialData[index + 2] = colorValue;
+ initialData[index + 3] = alphaValue;
+ }
+ }
+ }
+ return initialData;
+ }
+
+ initSourceWebGPUCanvas({
+ device,
+ canvasType,
+ width,
+ height,
+ alphaMode
+
+
+
+
+
+
+ })
+
+
+ {
+ const canvas = createCanvas(this, canvasType, width, height);
+
+ const gpuContext = canvas.getContext('webgpu');
+
+ if (!(gpuContext instanceof GPUCanvasContext)) {
+ this.skip(canvasType + ' canvas webgpu context not available');
+ }
+
+ gpuContext.configure({
+ device,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
+ alphaMode
+ });
+
+ // BGRA8Unorm texture
+ const initialData = this.getDataToInitSourceWebGPUCanvas(width, height, alphaMode);
+ const canvasTexture = gpuContext.getCurrentTexture();
+ device.queue.writeTexture(
+ { texture: canvasTexture },
+ initialData,
+ {
+ bytesPerRow: width * 4,
+ rowsPerImage: height
+ },
+ {
+ width,
+ height,
+ depthOrArrayLayers: 1
+ }
+ );
+
+ return {
+ canvas,
+ expectedSourceData: this.getExpectedReadbackForWebGPUCanvas(width, height, alphaMode)
+ };
+ }
+
+ getExpectedReadbackFor2DCanvas(
+ context,
+ width,
+ height)
+ {
+ // Always read back the raw data from canvas
+ return context.getImageData(0, 0, width, height).data;
+ }
+
+ getExpectedReadbackForWebGLCanvas(
+ gl,
+ width,
+ height)
+ {
+ const bytesPerPixel = 4;
+
+ const sourcePixels = new Uint8ClampedArray(width * height * bytesPerPixel);
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, sourcePixels);
+
+ return this.doFlipY(sourcePixels, width, height, bytesPerPixel);
+ }
+
+ getExpectedReadbackForWebGPUCanvas(
+ width,
+ height,
+ alphaMode)
+ {
+ const bytesPerPixel = 4;
+
+ const rgbaPixels = this.getDataToInitSourceWebGPUCanvas(width, height, alphaMode);
+
+ // The source canvas has bgra8unorm back resource. We
+ // swizzle the channels to align with 2d/webgl canvas and
+ // clear alpha to 255 (1.0) when context alphaMode
+ // is set to opaque (follow webgpu spec).
+ for (let i = 0; i < height; ++i) {
+ for (let j = 0; j < width; ++j) {
+ const pixelPos = i * width + j;
+ const r = rgbaPixels[pixelPos * bytesPerPixel + 2];
+ if (alphaMode === 'opaque') {
+ rgbaPixels[pixelPos * bytesPerPixel + 3] = 255;
+ }
+
+ rgbaPixels[pixelPos * bytesPerPixel + 2] = rgbaPixels[pixelPos * bytesPerPixel];
+ rgbaPixels[pixelPos * bytesPerPixel] = r;
+ }
+ }
+
+ return rgbaPixels;
+ }
+
+ doCopyContentsTest(
+ source,
+ expectedSourceImage,
+ p)
+
+
+
+
+
+
+
+ {
+ const dst = this.device.createTexture({
+ size: {
+ width: p.width,
+ height: p.height,
+ depthOrArrayLayers: 1
+ },
+ format: p.dstColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ // Construct expected value for different dst color format
+ const info = kTextureFormatInfo[p.dstColorFormat];
+ const expFormat = info.baseFormat ?? p.dstColorFormat;
+
+ // For 2d canvas, get expected pixels with getImageData(), which returns unpremultiplied
+ // values.
+ const expectedDestinationImage = this.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: expectedSourceImage,
+ srcOrigin: [0, 0],
+ srcSize: [p.width, p.height],
+ dstOrigin: [0, 0],
+ dstSize: [p.width, p.height],
+ subRectSize: [p.width, p.height],
+ format: expFormat,
+ flipSrcBeforeCopy: false,
+ srcDoFlipYDuringCopy: p.srcDoFlipYDuringCopy,
+ conversion: {
+ srcPremultiplied: p.srcPremultiplied,
+ dstPremultiplied: p.dstPremultiplied
+ }
+ });
+
+ this.doTestAndCheckResult(
+ { source, origin: { x: 0, y: 0 }, flipY: p.srcDoFlipYDuringCopy },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: p.dstPremultiplied
+ },
+ expectedDestinationImage,
+ { width: p.width, height: p.height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForNormFormat: 1, maxDiffULPsForFloatFormat: 1 }
+ );
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('copy_contents_from_2d_context_canvas').
+desc(
+ `
+ Test HTMLCanvasElement and OffscreenCanvas with 2d context
+ can be copied to WebGPU texture correctly.
+
+ It creates HTMLCanvasElement/OffscreenCanvas with '2d'.
+ Use fillRect(2d context) to render red rect for top-left,
+ green rect for top-right, blue rect for bottom-left and white for bottom-right.
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the canvas contents.
+
+ Provide premultiplied input if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and unpremultiplied input if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid canvas type
+ - Valid 2d context type
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+ - TODO(#913): color space tests need to be added
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T).
+combine('dstAlphaMode', kCanvasAlphaModes).
+combine('srcDoFlipYDuringCopy', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15]).
+combine('height', [1, 2, 4, 15])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+}).
+fn((t) => {
+ const { width, height, canvasType, dstAlphaMode } = t.params;
+
+ const { canvas, expectedSourceData } = t.init2DCanvasContent({
+ canvasType,
+ width,
+ height
+ });
+
+ t.doCopyContentsTest(canvas, expectedSourceData, {
+ srcPremultiplied: false,
+ dstPremultiplied: dstAlphaMode === 'premultiplied',
+ ...t.params
+ });
+});
+
+g.test('copy_contents_from_gl_context_canvas').
+desc(
+ `
+ Test HTMLCanvasElement and OffscreenCanvas with webgl/webgl2 context
+ can be copied to WebGPU texture correctly.
+
+ It creates HTMLCanvasElement/OffscreenCanvas with webgl'/'webgl2'.
+ Use scissor + clear to render red rect for top-left, green rect
+ for top-right, blue rect for bottom-left and white for bottom-right.
+ And do premultiply alpha in advance if the webgl/webgl2 context is created
+ with premultipliedAlpha : true.
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the canvas contents.
+
+ Provide premultiplied input if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and unpremultiplied input if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid canvas type
+ - Valid webgl/webgl2 context type
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage'(named 'srcDoFlipYDuringCopy' in cases)
+ - TODO: color space tests need to be added
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+combine('contextName', ['webgl', 'webgl2']).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T).
+combine('srcPremultiplied', [true, false]).
+combine('dstAlphaMode', kCanvasAlphaModes).
+combine('srcDoFlipYDuringCopy', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15]).
+combine('height', [1, 2, 4, 15])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+}).
+fn((t) => {
+ const { width, height, canvasType, contextName, srcPremultiplied, dstAlphaMode } = t.params;
+
+ const { canvas, expectedSourceData } = t.initGLCanvasContent({
+ canvasType,
+ contextName,
+ width,
+ height,
+ premultiplied: srcPremultiplied
+ });
+
+ t.doCopyContentsTest(canvas, expectedSourceData, {
+ dstPremultiplied: dstAlphaMode === 'premultiplied',
+ ...t.params
+ });
+});
+
+g.test('copy_contents_from_gpu_context_canvas').
+desc(
+ `
+ Test HTMLCanvasElement and OffscreenCanvas with webgpu context
+ can be copied to WebGPU texture correctly.
+
+ It creates HTMLCanvasElement/OffscreenCanvas with 'webgpu'.
+ Use writeTexture to copy pixels to back buffer. The results are:
+ red rect for top-left, green rect for top-right, blue rect for bottom-left
+ and white for bottom-right.
+
+ TODO: Actually test alphaMode = opaque.
+ And do premultiply alpha in advance if the webgpu context is created
+ with alphaMode="premultiplied".
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the canvas contents.
+
+ Provide premultiplied input if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and unpremultiplied input if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid canvas type
+ - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - TODO: test more source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage'(named 'srcDoFlipYDuringCopy' in cases)
+ - TODO: color space tests need to be added
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+combine('srcAndDstInSameGPUDevice', [true, false]).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T)
+// .combine('srcAlphaMode', kCanvasAlphaModes)
+.combine('srcAlphaMode', ['premultiplied']).
+combine('dstAlphaMode', kCanvasAlphaModes).
+combine('srcDoFlipYDuringCopy', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15]).
+combine('height', [1, 2, 4, 15])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn((t) => {
+ const { width, height, canvasType, srcAndDstInSameGPUDevice, srcAlphaMode, dstAlphaMode } =
+ t.params;
+
+ const device = srcAndDstInSameGPUDevice ? t.device : t.mismatchedDevice;
+ const { canvas: source, expectedSourceData } = t.initSourceWebGPUCanvas({
+ device,
+ canvasType,
+ width,
+ height,
+ alphaMode: srcAlphaMode
+ });
+
+ t.doCopyContentsTest(source, expectedSourceData, {
+ srcPremultiplied: srcAlphaMode === 'premultiplied',
+ dstPremultiplied: dstAlphaMode === 'premultiplied',
+ ...t.params
+ });
+});
+
+g.test('copy_contents_from_bitmaprenderer_context_canvas').
+desc(
+ `
+ Test HTMLCanvasElement and OffscreenCanvas with ImageBitmapRenderingContext
+ can be copied to WebGPU texture correctly.
+
+ It creates HTMLCanvasElement/OffscreenCanvas with 'bitmaprenderer'.
+ First, use fillRect(2d context) to render red rect for top-left,
+ green rect for top-right, blue rect for bottom-left and white for bottom-right on a
+ 2d context canvas and create imageBitmap with that canvas. Use transferFromImageBitmap()
+ to render the imageBitmap to source canvas.
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the canvas contents.
+
+ Provide premultiplied input if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and unpremultiplied input if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid canvas type
+ - Valid ImageBitmapRendering context type
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+ - TODO(#913): color space tests need to be added
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('canvasType', kAllCanvasTypes).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T).
+combine('dstAlphaMode', kCanvasAlphaModes).
+combine('srcDoFlipYDuringCopy', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15]).
+combine('height', [1, 2, 4, 15])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+}).
+fn(async (t) => {
+ const { width, height, canvasType, dstAlphaMode } = t.params;
+
+ const canvas = createCanvas(t, canvasType, width, height);
+
+ const imageBitmapRenderingContext = canvas.getContext('bitmaprenderer');
+
+ if (!(imageBitmapRenderingContext instanceof ImageBitmapRenderingContext)) {
+ skipTestCase(canvasType + ' canvas imageBitmap rendering context not available');
+ }
+
+ const { canvas: sourceContentCanvas, expectedSourceData } = t.init2DCanvasContent({
+ canvasType,
+ width,
+ height
+ });
+
+ const imageBitmap = await createImageBitmap(sourceContentCanvas, { premultiplyAlpha: 'none' });
+ imageBitmapRenderingContext.transferFromImageBitmap(imageBitmap);
+
+ t.doCopyContentsTest(canvas, expectedSourceData, {
+ srcPremultiplied: false,
+ dstPremultiplied: dstAlphaMode === 'premultiplied',
+ ...t.params
+ });
+});
+
+g.test('color_space_conversion').
+desc(
+ `
+ Test HTMLCanvasElement with 2d context can created with 'colorSpace' attribute.
+ Using CopyExternalImageToTexture to copy from such type of canvas needs
+ to do color space converting correctly.
+
+ It creates HTMLCanvasElement/OffscreenCanvas with '2d' and 'colorSpace' attributes.
+ Use fillRect(2d context) to render red rect for top-left,
+ green rect for top-right, blue rect for bottom-left and white for bottom-right.
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the canvas contents.
+
+ Provide premultiplied input if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and unpremultiplied input if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ If color space from source input and user defined dstTexture color space are different, the
+ result must convert the content to user defined color space
+
+ The tests covers:
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+ - Valid 'colorSpace' config in 'dstColorSpace'
+
+ And the expected results are all passed.
+
+ TODO: Enhance test data with colors that aren't always opaque and fully saturated.
+ TODO: Consider refactoring src data setup with TexelView.writeTextureData.
+ `
+).
+params((u) =>
+u.
+combine('srcColorSpace', ['srgb', 'display-p3']).
+combine('dstColorSpace', ['srgb']).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T).
+combine('dstPremultiplied', [true, false]).
+combine('srcDoFlipYDuringCopy', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15, 255, 256]).
+combine('height', [1, 2, 4, 15, 255, 256])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+}).
+fn((t) => {
+ const {
+ width,
+ height,
+ srcColorSpace,
+ dstColorSpace,
+ dstColorFormat,
+ dstPremultiplied,
+ srcDoFlipYDuringCopy
+ } = t.params;
+ const { canvas, expectedSourceData } = t.init2DCanvasContentWithColorSpace({
+ width,
+ height,
+ colorSpace: srcColorSpace
+ });
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const expectedDestinationImage = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: expectedSourceData,
+ srcOrigin: [0, 0],
+ srcSize: [width, height],
+ dstOrigin: [0, 0],
+ dstSize: [width, height],
+ subRectSize: [width, height],
+ // copyExternalImageToTexture does not perform gamma-encoding into `-srgb` formats.
+ format: kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat,
+ flipSrcBeforeCopy: false,
+ srcDoFlipYDuringCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied,
+ srcColorSpace,
+ dstColorSpace
+ }
+ });
+
+ const texelCompareOptions = {
+ maxFractionalDiff: 0,
+ maxDiffULPsForNormFormat: 1
+ };
+ if (srcColorSpace !== dstColorSpace) {
+ // Color space conversion seems prone to errors up to about 0.0003 on f32, 0.0007 on f16.
+ texelCompareOptions.maxFractionalDiff = 0.001;
+ } else {
+ texelCompareOptions.maxDiffULPsForFloatFormat = 1;
+ }
+
+ t.doTestAndCheckResult(
+ { source: canvas, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: dstColorSpace,
+ premultipliedAlpha: dstPremultiplied
+ },
+ expectedDestinationImage,
+ { width, height, depthOrArrayLayers: 1 },
+ texelCompareOptions
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/image.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/image.spec.js
new file mode 100644
index 0000000000..173bc9bbb7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/image.spec.js
@@ -0,0 +1,271 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyExternalImageToTexture from HTMLImageElement source.
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { raceWithRejectOnTimeout } from '../../../common/util/util.js';
+import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js';
+import { CopyToTextureUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
+
+import { kTestColorsOpaque, makeTestColorsTexelView } from './util.js';
+
+async function decodeImageFromCanvas(canvas) {
+ const blobFromCanvas = new Promise((resolve) => {
+ canvas.toBlob((blob) => resolve(blob));
+ });
+ const blob = await blobFromCanvas;
+ const url = URL.createObjectURL(blob);
+ const image = new Image(canvas.width, canvas.height);
+ image.src = url;
+ await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout');
+ return image;
+}
+
+export const g = makeTestGroup(CopyToTextureUtils);
+
+g.test('from_image').
+desc(
+ `
+ Test HTMLImageElement can be copied to WebGPU texture correctly.
+ These images are highly possible living in GPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White].
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the HTMLImageElement contents.
+
+ Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid 2D canvas
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('srcDoFlipYDuringCopy', [true, false]).
+combine('dstColorFormat', kValidTextureFormatsForCopyE2T).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('width', [1, 2, 4, 15, 255, 256]).
+combine('height', [1, 2, 4, 15, 255, 256])
+).
+beforeAllSubcases((t) => {
+ t.skipIfTextureFormatNotSupported(t.params.dstColorFormat);
+ if (typeof HTMLImageElement === 'undefined') t.skip('HTMLImageElement not available');
+}).
+fn(async (t) => {
+ const { width, height, dstColorFormat, dstPremultiplied, srcDoFlipYDuringCopy } = t.params;
+
+ const imageCanvas = document.createElement('canvas');
+ imageCanvas.width = width;
+ imageCanvas.height = height;
+
+ // Generate non-transparent pixel data to avoid canvas
+ // different opt behaviour on putImageData()
+ // from browsers.
+ const texelViewSource = makeTestColorsTexelView({
+ testColors: kTestColorsOpaque,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width,
+ height,
+ flipY: false,
+ premultiplied: false
+ });
+ // Generate correct expected values
+ const imageData = new ImageData(width, height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin: [0, 0],
+ subrectSize: { width, height }
+ });
+
+ const imageCanvasContext = imageCanvas.getContext('2d');
+ if (imageCanvasContext === null) {
+ t.skip('canvas cannot get 2d context');
+ return;
+ }
+ // Use putImageData to prevent color space conversion.
+ imageCanvasContext.putImageData(imageData, 0, 0);
+
+ const image = await decodeImageFromCanvas(imageCanvas);
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const expFormat = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat;
+ const flipSrcBeforeCopy = false;
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin: [0, 0],
+ srcSize: [width, height],
+ dstOrigin: [0, 0],
+ dstSize: [width, height],
+ subRectSize: [width, height],
+ format: expFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ {
+ source: image,
+ origin: { x: 0, y: 0 },
+ flipY: srcDoFlipYDuringCopy
+ },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ { width, height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+});
+
+g.test('copy_subrect_from_2D_Canvas').
+desc(
+ `
+ Test HTMLImageElement can be copied to WebGPU texture correctly.
+ These images are highly possible living in GPU back resource.
+
+ It generates pixels in ImageData one by one based on a color list:
+ [Red, Green, Blue, Black, White].
+
+ Then call copyExternalImageToTexture() to do a subrect copy, based on a predefined copy
+ rect info list, to the 0 mipLevel of dst texture, and read the contents out to compare
+ with the HTMLImageElement contents.
+
+ Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped, and origin is top-left consistantly.
+
+ The tests covers:
+ - Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+ - Valid subrect copies.
+
+ And the expected results are all passed.
+ `
+).
+params((u) =>
+u.
+combine('srcDoFlipYDuringCopy', [true, false]).
+combine('dstPremultiplied', [true, false]).
+beginSubcases().
+combine('copySubRectInfo', kCopySubrectInfo)
+).
+beforeAllSubcases((t) => {
+ if (typeof HTMLImageElement === 'undefined') t.skip('HTMLImageElement not available');
+}).
+fn(async (t) => {
+ const { copySubRectInfo, dstPremultiplied, srcDoFlipYDuringCopy } = t.params;
+
+ const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
+ const kColorFormat = 'rgba8unorm';
+
+ const imageCanvas = document.createElement('canvas');
+ imageCanvas.width = srcSize.width;
+ imageCanvas.height = srcSize.height;
+
+ // Generate non-transparent pixel data to avoid canvas
+ // different opt behaviour on putImageData()
+ // from browsers.
+ const texelViewSource = makeTestColorsTexelView({
+ testColors: kTestColorsOpaque,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width: srcSize.width,
+ height: srcSize.height,
+ flipY: false,
+ premultiplied: false
+ });
+ // Generate correct expected values
+ const imageData = new ImageData(srcSize.width, srcSize.height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: srcSize.width * 4,
+ rowsPerImage: srcSize.height,
+ subrectOrigin: [0, 0],
+ subrectSize: srcSize
+ });
+
+ const imageCanvasContext = imageCanvas.getContext('2d');
+ if (imageCanvasContext === null) {
+ t.skip('canvas cannot get 2d context');
+ return;
+ }
+ // Use putImageData to prevent color space conversion.
+ imageCanvasContext.putImageData(imageData, 0, 0);
+
+ const image = await decodeImageFromCanvas(imageCanvas);
+
+ const dst = t.device.createTexture({
+ size: dstSize,
+ format: kColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const flipSrcBeforeCopy = false;
+ const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
+ srcPixels: imageData.data,
+ srcOrigin,
+ srcSize,
+ dstOrigin,
+ dstSize,
+ subRectSize: copyExtent,
+ format: kColorFormat,
+ flipSrcBeforeCopy,
+ srcDoFlipYDuringCopy,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied
+ }
+ });
+
+ t.doTestAndCheckResult(
+ {
+ source: image,
+ origin: srcOrigin,
+ flipY: srcDoFlipYDuringCopy
+ },
+ {
+ texture: dst,
+ origin: dstOrigin,
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied
+ },
+ texelViewExpected,
+ copyExtent,
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/util.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/util.js
new file mode 100644
index 0000000000..d392f94d3d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/util.js
@@ -0,0 +1,58 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { TexelView } from '../../util/texture/texel_view.js';
+
+
+
+// None of the dst texture format is 'uint' or 'sint', so we can always use float value.
+const kColors = {
+ 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 },
+ Black: { R: 0.0, G: 0.0, B: 0.0, A: 1.0 },
+ White: { R: 1.0, G: 1.0, B: 1.0, A: 1.0 },
+ SemitransparentWhite: { R: 1.0, G: 1.0, B: 1.0, A: 0.6 }
+};
+
+export const kTestColorsOpaque = [
+kColors.Red,
+kColors.Green,
+kColors.Blue,
+kColors.Black,
+kColors.White];
+
+
+export const kTestColorsAll = [...kTestColorsOpaque, kColors.SemitransparentWhite];
+
+export function makeTestColorsTexelView({
+ testColors,
+ format,
+ width,
+ height,
+ premultiplied,
+ flipY
+
+
+
+
+
+
+
+}) {
+ return TexelView.fromTexelsAsColors(format, (coords) => {
+ const y = flipY ? height - coords.y - 1 : coords.y;
+ const pixelPos = y * width + coords.x;
+ const currentPixel = testColors[pixelPos % testColors.length];
+
+ if (premultiplied && currentPixel.A !== 1.0) {
+ return {
+ R: currentPixel.R * currentPixel.A,
+ G: currentPixel.G * currentPixel.A,
+ B: currentPixel.B * currentPixel.A,
+ A: currentPixel.A
+ };
+ } else {
+ return currentPixel;
+ }
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/video.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/video.spec.js
new file mode 100644
index 0000000000..9df16bf5ef
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/copyToTexture/video.spec.js
@@ -0,0 +1,119 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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)
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUTest, TextureTestMixin } from '../../gpu_test.js';
+import {
+ startPlayingAndWaitForVideo,
+ getVideoElement,
+ getVideoFrameFromVideoElement,
+ kVideoExpectations } from
+'../../web_platform/util.js';
+
+const kFormat = 'rgba8unorm';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+g.test('copy_from_video').
+desc(
+ `
+Test HTMLVideoElement and VideoFrame can be copied to WebGPU texture correctly.
+
+It creates HTMLVideoElement with videos under Resource folder.
+
+ Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
+ of dst texture, and read the contents out to compare with the ImageBitmap contents.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Video comes from different color spaces.
+ - 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('sourceType', ['VideoElement', 'VideoFrame']).
+combine('srcDoFlipYDuringCopy', [true, false])
+).
+fn(async (t) => {
+ const { videoName, sourceType, srcDoFlipYDuringCopy } = t.params;
+
+ if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
+ t.skip('WebCodec is not supported');
+ }
+
+ const videoElement = getVideoElement(t, videoName);
+
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ let source, width, height;
+ if (sourceType === 'VideoFrame') {
+ source = await getVideoFrameFromVideoElement(t, videoElement);
+ width = source.codedWidth;
+ height = source.codedHeight;
+ } else {
+ source = videoElement;
+ width = source.videoWidth;
+ height = source.videoHeight;
+ }
+
+ const dstTexture = t.device.createTexture({
+ format: kFormat,
+ size: { width, height, depthOrArrayLayers: 1 },
+ usage:
+ GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.queue.copyExternalImageToTexture(
+ {
+ source,
+ origin: { x: 0, y: 0 },
+ flipY: srcDoFlipYDuringCopy
+ },
+ {
+ texture: dstTexture,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: true
+ },
+ { width, height, depthOrArrayLayers: 1 }
+ );
+
+ 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 }]
+ );
+ } 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 }]
+ );
+ }
+
+ if (source instanceof VideoFrame) {
+ source.close();
+ }
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/external_texture/video.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/external_texture/video.spec.js
new file mode 100644
index 0000000000..80acbb5d5e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/external_texture/video.spec.js
@@ -0,0 +1,480 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/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)
+
+TODO: consider whether external_texture and copyToTexture video tests should be in the same file
+`;import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUTest, TextureTestMixin } from '../../gpu_test.js';
+import {
+ startPlayingAndWaitForVideo,
+ getVideoFrameFromVideoElement,
+ getVideoElement,
+ kVideoExpectations,
+ kVideoRotationExpectations } from
+'../../web_platform/util.js';
+
+const kHeight = 16;
+const kWidth = 16;
+const kFormat = 'rgba8unorm';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+function createExternalTextureSamplingTestPipeline(t) {
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ var pos = array<vec4<f32>, 6>(
+ vec4<f32>( 1.0, 1.0, 0.0, 1.0),
+ vec4<f32>( 1.0, -1.0, 0.0, 1.0),
+ vec4<f32>(-1.0, -1.0, 0.0, 1.0),
+ vec4<f32>( 1.0, 1.0, 0.0, 1.0),
+ vec4<f32>(-1.0, -1.0, 0.0, 1.0),
+ vec4<f32>(-1.0, 1.0, 0.0, 1.0)
+ );
+ return pos[VertexIndex];
+ }
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_external;
+
+ @fragment fn main(@builtin(position) FragCoord : vec4<f32>)
+ -> @location(0) vec4<f32> {
+ return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2<f32>(16.0, 16.0));
+ }
+ `
+ }),
+ entryPoint: 'main',
+ targets: [
+ {
+ format: kFormat
+ }]
+
+ },
+ primitive: { topology: 'triangle-list' }
+ });
+
+ return pipeline;
+}
+
+function createExternalTextureSamplingTestBindGroup(
+t,
+checkNonStandardIsZeroCopy,
+source,
+pipeline)
+{
+ const linearSampler = t.device.createSampler();
+
+ const externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+
+ if (checkNonStandardIsZeroCopy) {
+ expectZeroCopyNonStandard(t, externalTexture);
+ }
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: linearSampler
+ },
+ {
+ binding: 1,
+ resource: externalTexture
+ }]
+
+ });
+
+ return bindGroup;
+}
+
+/**
+ * Expect the non-standard `externalTexture.isZeroCopy` is true.
+ */
+function expectZeroCopyNonStandard(t, externalTexture) {
+
+ t.expect(externalTexture.isZeroCopy, '0-copy import failed.');
+}
+
+/**
+ * `externalTexture.isZeroCopy` is a non-standard Chrome API for testing only.
+ * It is exposed by enabling chrome://flags/#enable-webgpu-developer-features
+ *
+ * If the API is available, this function adds a parameter `checkNonStandardIsZeroCopy`.
+ * Cases with that parameter set to `true` will fail if `externalTexture.isZeroCopy` is not true.
+ */
+function checkNonStandardIsZeroCopyIfAvailable() {
+ if (
+ typeof GPUExternalTexture !== 'undefined' &&
+
+ GPUExternalTexture.prototype.hasOwnProperty('isZeroCopy'))
+ {
+ return [{}, { checkNonStandardIsZeroCopy: true }];
+ } else {
+ return [{}];
+ }
+}
+
+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.
+`
+).
+params((u) =>
+u //
+.combineWithParams(checkNonStandardIsZeroCopyIfAvailable()).
+combine('sourceType', ['VideoElement', 'VideoFrame']).
+combineWithParams(kVideoExpectations)
+).
+fn(async (t) => {
+ const sourceType = t.params.sourceType;
+ if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
+ t.skip('WebCodec is not supported');
+ }
+
+ const videoElement = getVideoElement(t, t.params.videoName);
+
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+
+ const colorAttachment = t.device.createTexture({
+ format: kFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const pipeline = createExternalTextureSamplingTestPipeline(t);
+ const bindGroup = createExternalTextureSamplingTestBindGroup(
+ t,
+ t.params.checkNonStandardIsZeroCopy,
+ source,
+ pipeline
+ );
+
+ 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()]);
+
+ // 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 }]
+ );
+
+ if (sourceType === 'VideoFrame') source.close();
+ });
+});
+
+g.test('importExternalTexture,sampleWithRotationMetadata').
+desc(
+ `
+Tests that when importing an HTMLVideoElement/VideoFrame into a GPUExternalTexture, sampling from
+it will honor rotation metadata.
+`
+).
+params((u) =>
+u //
+.combineWithParams(checkNonStandardIsZeroCopyIfAvailable()).
+combine('sourceType', ['VideoElement', 'VideoFrame']).
+combineWithParams(kVideoRotationExpectations)
+).
+fn(async (t) => {
+ const sourceType = t.params.sourceType;
+ const videoElement = getVideoElement(t, t.params.videoName);
+
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+
+ const colorAttachment = t.device.createTexture({
+ format: kFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const pipeline = createExternalTextureSamplingTestPipeline(t);
+ const bindGroup = createExternalTextureSamplingTestBindGroup(
+ t,
+ t.params.checkNonStandardIsZeroCopy,
+ source,
+ pipeline
+ );
+
+ 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()]);
+
+ // 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 }]
+ );
+
+ if (sourceType === 'VideoFrame') source.close();
+ });
+});
+
+g.test('importExternalTexture,sampleWithVideoFrameWithVisibleRectParam').
+desc(
+ `
+Tests that we can import VideoFrames and sample the correct sub-rectangle when visibleRect
+parameters are present.
+`
+).
+params((u) =>
+u //
+.combineWithParams(checkNonStandardIsZeroCopyIfAvailable()).
+combineWithParams(kVideoExpectations)
+).
+fn(async (t) => {
+ const videoElement = getVideoElement(t, t.params.videoName);
+
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source = await getVideoFrameFromVideoElement(t, videoElement);
+
+ // 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;
+ const cropParams = [
+ // Top left (yellow)
+ {
+ subRect: { x: 0, y: 0, width: srcVideoWidth / 2, height: srcVideoHeight / 2 },
+ color: t.params._yellowExpectation
+ },
+ // Top right (red)
+ {
+ subRect: {
+ x: srcVideoWidth / 2,
+ y: 0,
+ width: srcVideoWidth / 2,
+ height: srcVideoHeight / 2
+ },
+ color: t.params._redExpectation
+ },
+ // Bottom left (blue)
+ {
+ subRect: {
+ x: 0,
+ y: srcVideoHeight / 2,
+ width: srcVideoWidth / 2,
+ height: srcVideoHeight / 2
+ },
+ color: t.params._blueExpectation
+ },
+ // Bottom right (green)
+ {
+ subRect: {
+ x: srcVideoWidth / 2,
+ y: srcVideoHeight / 2,
+ width: srcVideoWidth / 2,
+ height: srcVideoHeight / 2
+ },
+ color: t.params._greenExpectation
+ }];
+
+
+ for (const cropParam of cropParams) {
+ // MAINTENANCE_TODO: remove cast with TypeScript 4.9.6+.
+
+ const subRect = new VideoFrame(source, { visibleRect: cropParam.subRect });
+
+ const colorAttachment = t.device.createTexture({
+ format: kFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const pipeline = createExternalTextureSamplingTestPipeline(t);
+ const bindGroup = createExternalTextureSamplingTestBindGroup(
+ t,
+ t.params.checkNonStandardIsZeroCopy,
+ subRect,
+ pipeline
+ );
+
+ 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()]);
+
+ // For validation, we sample a few pixels away from the edges to avoid compression
+ // artifacts.
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
+ { coord: { x: kWidth * 0.1, y: kHeight * 0.1 }, exp: cropParam.color },
+ { coord: { x: kWidth * 0.9, y: kHeight * 0.1 }, exp: cropParam.color },
+ { coord: { x: kWidth * 0.1, y: kHeight * 0.9 }, exp: cropParam.color },
+ { coord: { x: kWidth * 0.9, y: kHeight * 0.9 }, exp: cropParam.color }]
+ );
+
+ subRect.close();
+ }
+
+ source.close();
+ });
+});
+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.
+`
+).
+params((u) =>
+u //
+.combineWithParams(checkNonStandardIsZeroCopyIfAvailable()).
+combine('sourceType', ['VideoElement', 'VideoFrame']).
+combineWithParams(kVideoExpectations)
+).
+fn(async (t) => {
+ const sourceType = t.params.sourceType;
+ if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
+ t.skip('WebCodec is not supported');
+ }
+
+ const videoElement = getVideoElement(t, t.params.videoName);
+
+ await startPlayingAndWaitForVideo(videoElement, async () => {
+ const source =
+ sourceType === 'VideoFrame' ?
+ await getVideoFrameFromVideoElement(t, videoElement) :
+ videoElement;
+ const externalTexture = t.device.importExternalTexture({
+
+ source: source
+ });
+ if (t.params.checkNonStandardIsZeroCopy) {
+ expectZeroCopyNonStandard(t, externalTexture);
+ }
+ const outputTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [2, 2, 1],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING
+ });
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ // Shader loads 4 pixels near each corner, and then store them in a storage texture.
+ module: t.device.createShaderModule({
+ code: `
+ @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));
+ textureStore(outImage, vec2<i32>(0, 0), yellow);
+ var red : vec4<f32> = textureLoad(t, vec2<i32>(240, 60));
+ textureStore(outImage, vec2<i32>(0, 1), red);
+ var blue : vec4<f32> = textureLoad(t, vec2<i32>(80, 180));
+ textureStore(outImage, vec2<i32>(1, 0), blue);
+ var green : vec4<f32> = textureLoad(t, vec2<i32>(240, 180));
+ textureStore(outImage, vec2<i32>(1, 1), green);
+ return;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const bg = t.device.createBindGroup({
+ entries: [
+ { binding: 0, resource: externalTexture },
+ { binding: 1, resource: outputTexture.createView() }],
+
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ 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 }]
+ );
+
+ if (sourceType === 'VideoFrame') source.close();
+ });
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.html.js
new file mode 100644
index 0000000000..20ef1ec3a4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.html.js
@@ -0,0 +1,34 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { runRefTest } from './gpu_ref_test.js';runRefTest((t) => {
+ function draw(canvasId, format) {
+ const canvas = document.getElementById(canvasId);
+
+ const ctx = canvas.getContext('webgpu');
+ ctx.configure({
+ device: t.device,
+ format
+ });
+
+ const colorAttachment = ctx.getCurrentTexture();
+ const colorAttachmentView = colorAttachment.createView();
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachmentView,
+ clearValue: { r: 0.4, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ draw('cvs0', 'bgra8unorm');
+ draw('cvs1', 'rgba8unorm');
+ draw('cvs2', 'rgba16float');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.https.html
new file mode 100644
index 0000000000..3639d3ca82
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_clear.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_clear</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta name="assert" content="WebGPU cleared canvas should be presented correctly" />
+ <link rel="match" href="./ref/canvas_clear-ref.html" />
+ <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs1" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs2" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module" src="canvas_clear.html.js"></script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace.html.js
new file mode 100644
index 0000000000..7f579ae70d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace.html.js
@@ -0,0 +1,139 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kUnitCaseParamsBuilder } from '../../../common/framework/params_builder.js';import { Float16Array } from '../../../external/petamoriken/float16/float16.js';import { kCanvasAlphaModes, kCanvasColorSpaces } from '../../capability_info.js';
+
+import { runRefTest } from './gpu_ref_test.js';
+
+function bgra8UnormFromRgba8Unorm(rgba8Unorm) {
+ // This is used only once. May need to optimize if reused.
+ const bgra8Unorm = rgba8Unorm.slice();
+ for (let i = 0; i < bgra8Unorm.length; i += 4) {
+ [bgra8Unorm[i], bgra8Unorm[i + 2]] = [bgra8Unorm[i + 2], bgra8Unorm[i]];
+ }
+ return bgra8Unorm;
+}
+
+function rgba16floatFromRgba8unorm(rgba8Unorm) {
+ // This is used only once. May need to optimize if reused.
+ const rgba16Float = new Float16Array(rgba8Unorm.length);
+ for (let i = 0; i < rgba8Unorm.length; ++i) {
+ rgba16Float[i] = rgba8Unorm[i] / 255;
+ }
+ return rgba16Float;
+}
+
+
+
+
+
+
+
+
+
+function render(
+device,
+{ canvas, format, alphaMode, colorSpace, textureData })
+{
+ const context = canvas.getContext('webgpu');
+ context.configure({
+ device,
+ format,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ alphaMode,
+ colorSpace
+ });
+
+ const texture = context.getCurrentTexture();
+ device.queue.writeTexture({ texture }, textureData, {}, { width: 4, height: 1 });
+}
+
+export function runColorSpaceTest(format) {
+ runRefTest(async (t) => {
+
+ const kRGBA8UnormData = new Uint8Array([
+ 0, 255, 0, 255,
+ 117, 251, 7, 255,
+ 170, 35, 209, 255,
+ 80, 150, 200, 255]
+ );
+ const kBGRA8UnormData = bgra8UnormFromRgba8Unorm(kRGBA8UnormData);
+ const kRGBA16FloatData = rgba16floatFromRgba8unorm(kRGBA8UnormData);
+ const width = kRGBA8UnormData.length / 4;
+
+ const testData = {
+ rgba8unorm: kRGBA8UnormData,
+ bgra8unorm: kBGRA8UnormData,
+ rgba16float: kRGBA16FloatData
+ };
+ const textureData = testData[format].buffer;
+
+ async function createCanvas(
+ creation,
+ alphaMode,
+ format,
+ colorSpace)
+ {
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = 1;
+ document.body.appendChild(canvas);
+
+ switch (creation) {
+ case 'canvas':
+ render(t.device, { canvas, format, alphaMode, colorSpace, textureData });
+ break;
+
+ case 'transferControlToOffscreen':{
+ const offscreenCanvas = canvas.transferControlToOffscreen();
+ render(t.device, { canvas: offscreenCanvas, format, alphaMode, colorSpace, textureData });
+ break;
+ }
+
+ case 'transferControlToOffscreenWorker':{
+ const offscreenCanvas = canvas.transferControlToOffscreen();
+ const source = `
+ ${render.toString()}
+
+ onmessage = async (event) => {
+ try {
+ const adapter = await navigator.gpu.requestAdapter();
+ const device = await adapter.requestDevice();
+ render(device, event.data);
+ postMessage(true);
+ } catch (e) {
+ postMessage(false);
+ }
+ };
+ `;
+ const blob = new Blob([source], { type: 'application/javascript' });
+ const url = URL.createObjectURL(blob);
+ const worker = new Worker(url);
+ let resolve;
+ const promise = new Promise((_resolve) => resolve = _resolve);
+ worker.onmessage = (event) => {
+ resolve(event.data);
+ };
+ worker.postMessage(
+ { canvas: offscreenCanvas, format, alphaMode, colorSpace, textureData },
+ [offscreenCanvas]
+ );
+ await promise;
+ break;
+ }
+ }
+ }
+
+ const u = kUnitCaseParamsBuilder.
+ combine('alphaMode', kCanvasAlphaModes).
+ combine('colorSpace', kCanvasColorSpaces).
+ combine('creation', [
+ 'canvas',
+ 'transferControlToOffscreen',
+ 'transferControlToOffscreenWorker']
+ );
+
+ for (const { alphaMode, colorSpace, creation } of u) {
+ await createCanvas(creation, alphaMode, format, colorSpace);
+ }
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
new file mode 100644
index 0000000000..c910c97b1d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_colorspace_bgra8unorm</title>
+ <meta charset="utf-8" />
+ <style>
+ canvas {
+ width: 128px;
+ height: 128px;
+ margin-right: 5px;
+ image-rendering: pixelated;
+ image-rendering: crisp-edges;
+ }
+ </style>
+ <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" />
+ <script type="module">
+ import { runColorSpaceTest } from './canvas_colorspace.html.js';
+ runColorSpaceTest('bgra8unorm');
+ </script>
+ <body></body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
new file mode 100644
index 0000000000..7f57858e49
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_colorspace_rgba16float</title>
+ <meta charset="utf-8" />
+ <style>
+ canvas {
+ width: 128px;
+ height: 128px;
+ margin-right: 5px;
+ image-rendering: pixelated;
+ image-rendering: crisp-edges;
+ }
+ </style>
+ <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">
+ <script type="module">
+ import { runColorSpaceTest } from './canvas_colorspace.html.js';
+ runColorSpaceTest('rgba16float');
+ </script>
+ <body></body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
new file mode 100644
index 0000000000..e57e04ef5c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_colorspace_rgba8unorm</title>
+ <meta charset="utf-8" />
+ <style>
+ canvas {
+ width: 128px;
+ height: 128px;
+ margin-right: 5px;
+ image-rendering: pixelated;
+ image-rendering: crisp-edges;
+ }
+ </style>
+ <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" />
+ <script type="module">
+ import { runColorSpaceTest } from './canvas_colorspace.html.js';
+ runColorSpaceTest('rgba8unorm');
+ </script>
+ <body></body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex.html.js
new file mode 100644
index 0000000000..cc65c8985c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex.html.js
@@ -0,0 +1,772 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../common/util/util.js';import { kTextureFormatInfo } from '../../format_info.js';import { gammaDecompress, float32ToFloat16Bits } from '../../util/conversion.js';
+import { align } from '../../util/math.js';
+
+import { runRefTest } from './gpu_ref_test.js';
+
+
+
+
+
+
+
+
+
+
+
+
+export function run(
+format,
+targets)
+{
+ runRefTest(async (t) => {
+ let shaderValue = 0x66 / 0xff;
+ let isOutputSrgb = false;
+ switch (format) {
+ case 'bgra8unorm':
+ case 'rgba8unorm':
+ case 'rgba16float':
+ break;
+ case 'bgra8unorm-srgb':
+ case 'rgba8unorm-srgb':
+ // NOTE: "-srgb" cases haven't been tested (there aren't any .html files that use them).
+
+ // Reverse gammaCompress to get same value shader output as non-srgb formats:
+ shaderValue = gammaDecompress(shaderValue);
+ isOutputSrgb = true;
+ break;
+ default:
+ unreachable();
+ }
+ const shaderValueStr = shaderValue.toFixed(5);
+
+ function copyBufferToTexture(ctx) {
+ const rows = ctx.canvas.height;
+ const bytesPerPixel = kTextureFormatInfo[format].color.bytes;
+ if (bytesPerPixel === undefined) {
+ unreachable();
+ }
+ const bytesPerRow = align(bytesPerPixel * ctx.canvas.width, 256);
+ const componentsPerPixel = 4;
+
+ const buffer = t.device.createBuffer({
+ mappedAtCreation: true,
+ size: rows * bytesPerRow,
+ usage: GPUBufferUsage.COPY_SRC
+ });
+ // These are run only once per test, so there are no wasted reallocations below.
+ let red;
+ let green;
+ let blue;
+ let yellow;
+
+ const mapping = buffer.getMappedRange();
+ let data;
+ switch (format) {
+ case 'bgra8unorm':
+ case 'bgra8unorm-srgb':
+ {
+ data = new Uint8Array(mapping);
+ red = new Uint8Array([0x00, 0x00, 0x66, 0xff]);
+ green = new Uint8Array([0x00, 0x66, 0x00, 0xff]);
+ blue = new Uint8Array([0x66, 0x00, 0x00, 0xff]);
+ yellow = new Uint8Array([0x00, 0x66, 0x66, 0xff]);
+ }
+ break;
+ case 'rgba8unorm':
+ case 'rgba8unorm-srgb':
+ {
+ data = new Uint8Array(mapping);
+ red = new Uint8Array([0x66, 0x00, 0x00, 0xff]);
+ green = new Uint8Array([0x00, 0x66, 0x00, 0xff]);
+ blue = new Uint8Array([0x00, 0x00, 0x66, 0xff]);
+ yellow = new Uint8Array([0x66, 0x66, 0x00, 0xff]);
+ }
+ break;
+ case 'rgba16float':
+ {
+ data = new Uint16Array(mapping);
+ red = new Uint16Array([
+ float32ToFloat16Bits(0.4),
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(1.0)]
+ );
+ green = new Uint16Array([
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(0.4),
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(1.0)]
+ );
+ blue = new Uint16Array([
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(0.4),
+ float32ToFloat16Bits(1.0)]
+ );
+ yellow = new Uint16Array([
+ float32ToFloat16Bits(0.4),
+ float32ToFloat16Bits(0.4),
+ float32ToFloat16Bits(0.0),
+ float32ToFloat16Bits(1.0)]
+ );
+ }
+ break;
+ default:
+ unreachable();
+ }
+ for (let i = 0; i < ctx.canvas.width; ++i)
+ for (let j = 0; j < ctx.canvas.height; ++j) {
+ let pixel;
+ if (i < ctx.canvas.width / 2) {
+ if (j < ctx.canvas.height / 2) {
+ pixel = red;
+ } else {
+ pixel = blue;
+ }
+ } else {
+ if (j < ctx.canvas.height / 2) {
+ pixel = green;
+ } else {
+ pixel = yellow;
+ }
+ }
+ data.set(pixel, (i + j * (bytesPerRow / bytesPerPixel)) * componentsPerPixel);
+ }
+ buffer.unmap();
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture({ buffer, bytesPerRow }, { texture: ctx.getCurrentTexture() }, [
+ ctx.canvas.width,
+ ctx.canvas.height,
+ 1]
+ );
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ function getImageBitmap(ctx) {
+ const data = new Uint8ClampedArray(ctx.canvas.width * ctx.canvas.height * 4);
+ for (let i = 0; i < ctx.canvas.width; ++i)
+ for (let j = 0; j < ctx.canvas.height; ++j) {
+ const offset = (i + j * ctx.canvas.width) * 4;
+ if (i < ctx.canvas.width / 2) {
+ if (j < ctx.canvas.height / 2) {
+ data.set([0x66, 0x00, 0x00, 0xff], offset);
+ } else {
+ data.set([0x00, 0x00, 0x66, 0xff], offset);
+ }
+ } else {
+ if (j < ctx.canvas.height / 2) {
+ data.set([0x00, 0x66, 0x00, 0xff], offset);
+ } else {
+ data.set([0x66, 0x66, 0x00, 0xff], offset);
+ }
+ }
+ }
+ const imageData = new ImageData(data, ctx.canvas.width, ctx.canvas.height);
+ return createImageBitmap(imageData);
+ }
+
+ function setupSrcTexture(imageBitmap) {
+ const [srcWidth, srcHeight] = [imageBitmap.width, imageBitmap.height];
+ const srcTexture = t.device.createTexture({
+ size: [srcWidth, srcHeight, 1],
+ format,
+ usage:
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.COPY_SRC
+ });
+ t.device.queue.copyExternalImageToTexture({ source: imageBitmap }, { texture: srcTexture }, [
+ imageBitmap.width,
+ imageBitmap.height]
+ );
+ return srcTexture;
+ }
+
+ async function copyExternalImageToTexture(ctx) {
+ const imageBitmap = await getImageBitmap(ctx);
+ t.device.queue.copyExternalImageToTexture(
+ { source: imageBitmap },
+ { texture: ctx.getCurrentTexture() },
+ [imageBitmap.width, imageBitmap.height]
+ );
+ }
+
+ async function copyTextureToTexture(ctx) {
+ const imageBitmap = await getImageBitmap(ctx);
+ const srcTexture = setupSrcTexture(imageBitmap);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture(
+ { texture: srcTexture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { texture: ctx.getCurrentTexture(), mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ [imageBitmap.width, imageBitmap.height, 1]
+ );
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ async function DrawTextureSample(ctx) {
+ const imageBitmap = await getImageBitmap(ctx);
+ const srcTexture = setupSrcTexture(imageBitmap);
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+struct VertexOutput {
+ @builtin(position) Position : vec4<f32>,
+ @location(0) fragUV : vec2<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 uv = array<vec2<f32>, 6>(
+ vec2<f32>(1.0, 0.0),
+ vec2<f32>(1.0, 1.0),
+ vec2<f32>(0.0, 1.0),
+ vec2<f32>(1.0, 0.0),
+ vec2<f32>(0.0, 1.0),
+ vec2<f32>(0.0, 0.0));
+
+ var output : VertexOutput;
+ output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ output.fragUV = uv[VertexIndex];
+ return output;
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ // NOTE: "-srgb" cases haven't been tested (there aren't any .html files that use them).
+ code: `
+@group(0) @binding(0) var mySampler: sampler;
+@group(0) @binding(1) var myTexture: texture_2d<f32>;
+
+fn gammaDecompress(n: f32) -> f32 {
+ var r = n;
+ if (r <= 0.04045) {
+ r = r * 25.0 / 323.0;
+ } else {
+ r = pow((200.0 * r + 11.0) / 121.0, 12.0 / 5.0);
+ }
+ r = clamp(r, 0.0, 1.0);
+ return r;
+}
+
+@fragment
+fn srgbMain(@location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> {
+ var result = textureSample(myTexture, mySampler, fragUV);
+ result.r = gammaDecompress(result.r);
+ result.g = gammaDecompress(result.g);
+ result.b = gammaDecompress(result.b);
+ return result;
+}
+
+@fragment
+fn linearMain(@location(0) fragUV: vec2<f32>) -> @location(0) vec4<f32> {
+ return textureSample(myTexture, mySampler, fragUV);
+}
+ `
+ }),
+ entryPoint: isOutputSrgb ? 'srgbMain' : 'linearMain',
+ targets: [{ format }]
+ },
+ primitive: {
+ topology: 'triangle-list'
+ }
+ });
+
+ const sampler = t.device.createSampler({
+ magFilter: 'nearest',
+ minFilter: 'nearest'
+ });
+
+ const uniformBindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: sampler
+ },
+ {
+ binding: 1,
+ resource: srcTexture.createView()
+ }]
+
+ });
+
+ const renderPassDescriptor = {
+ colorAttachments: [
+ {
+ view: ctx.getCurrentTexture().createView(),
+
+ clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+ passEncoder.setPipeline(pipeline);
+ passEncoder.setBindGroup(0, uniformBindGroup);
+ passEncoder.draw(6, 1, 0, 0);
+ passEncoder.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+
+ function DrawVertexColor(ctx) {
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+struct VertexOutput {
+ @builtin(position) Position : vec4<f32>,
+ @location(0) fragColor : vec4<f32>,
+}
+
+@vertex
+fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
+ var pos = array<vec2<f32>, 6>(
+ vec2<f32>( 0.5, 0.5),
+ vec2<f32>( 0.5, -0.5),
+ vec2<f32>(-0.5, -0.5),
+ vec2<f32>( 0.5, 0.5),
+ vec2<f32>(-0.5, -0.5),
+ vec2<f32>(-0.5, 0.5));
+
+ var offset = array<vec2<f32>, 4>(
+ vec2<f32>( -0.5, 0.5),
+ vec2<f32>( 0.5, 0.5),
+ vec2<f32>(-0.5, -0.5),
+ vec2<f32>( 0.5, -0.5));
+
+ var color = array<vec4<f32>, 4>(
+ vec4<f32>(${shaderValueStr}, 0.0, 0.0, 1.0),
+ vec4<f32>(0.0, ${shaderValueStr}, 0.0, 1.0),
+ vec4<f32>(0.0, 0.0, ${shaderValueStr}, 1.0),
+ vec4<f32>(${shaderValueStr}, ${shaderValueStr}, 0.0, 1.0));
+
+ var output : VertexOutput;
+ output.Position = vec4<f32>(pos[VertexIndex % 6u] + offset[VertexIndex / 6u], 0.0, 1.0);
+ output.fragColor = color[VertexIndex / 6u];
+ return output;
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+@fragment
+fn main(@location(0) fragColor: vec4<f32>) -> @location(0) vec4<f32> {
+ return fragColor;
+}
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ primitive: {
+ topology: 'triangle-list'
+ }
+ });
+
+ const renderPassDescriptor = {
+ colorAttachments: [
+ {
+ view: ctx.getCurrentTexture().createView(),
+
+ clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+ passEncoder.setPipeline(pipeline);
+ passEncoder.draw(24, 1, 0, 0);
+ passEncoder.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+
+ function DrawFragcoord(ctx) {
+ const halfCanvasWidthStr = (ctx.canvas.width / 2).toFixed();
+ const halfCanvasHeightStr = (ctx.canvas.height / 2).toFixed();
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+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;
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+@group(0) @binding(0) var mySampler: sampler;
+@group(0) @binding(1) var myTexture: texture_2d<f32>;
+
+@fragment
+fn main(@builtin(position) fragcoord: vec4<f32>) -> @location(0) vec4<f32> {
+ var coord = vec2<u32>(floor(fragcoord.xy));
+ var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ if (coord.x < ${halfCanvasWidthStr}u) {
+ if (coord.y < ${halfCanvasHeightStr}u) {
+ color.r = ${shaderValueStr};
+ } else {
+ color.b = ${shaderValueStr};
+ }
+ } else {
+ if (coord.y < ${halfCanvasHeightStr}u) {
+ color.g = ${shaderValueStr};
+ } else {
+ color.r = ${shaderValueStr};
+ color.g = ${shaderValueStr};
+ }
+ }
+ return color;
+}
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ primitive: {
+ topology: 'triangle-list'
+ }
+ });
+
+ const renderPassDescriptor = {
+ colorAttachments: [
+ {
+ view: ctx.getCurrentTexture().createView(),
+
+ clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+ passEncoder.setPipeline(pipeline);
+ passEncoder.draw(6, 1, 0, 0);
+ passEncoder.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+
+ function FragmentTextureStore(ctx) {
+ const halfCanvasWidthStr = (ctx.canvas.width / 2).toFixed();
+ const halfCanvasHeightStr = (ctx.canvas.height / 2).toFixed();
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+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;
+}
+ `
+ }),
+ entryPoint: 'main'
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+@group(0) @binding(0) var outImage : texture_storage_2d<${format}, write>;
+
+@fragment
+fn main(@builtin(position) fragcoord: vec4<f32>) -> @location(0) vec4<f32> {
+ var coord = vec2<u32>(floor(fragcoord.xy));
+ var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ if (coord.x < ${halfCanvasWidthStr}u) {
+ if (coord.y < ${halfCanvasHeightStr}u) {
+ color.r = ${shaderValueStr};
+ } else {
+ color.b = ${shaderValueStr};
+ }
+ } else {
+ if (coord.y < ${halfCanvasHeightStr}u) {
+ color.g = ${shaderValueStr};
+ } else {
+ color.r = ${shaderValueStr};
+ color.g = ${shaderValueStr};
+ }
+ }
+ textureStore(outImage, vec2<i32>(coord), color);
+ return color;
+}
+ `
+ }),
+ entryPoint: 'main',
+ targets: [{ format }]
+ },
+ primitive: {
+ topology: 'triangle-list'
+ }
+ });
+
+ const bg = t.device.createBindGroup({
+ entries: [{ binding: 0, resource: ctx.getCurrentTexture().createView() }],
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const outputTexture = t.device.createTexture({
+ format,
+ size: [ctx.canvas.width, ctx.canvas.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const renderPassDescriptor = {
+ colorAttachments: [
+ {
+ view: outputTexture.createView(),
+
+ clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+ passEncoder.setPipeline(pipeline);
+ passEncoder.setBindGroup(0, bg);
+ passEncoder.draw(6, 1, 0, 0);
+ passEncoder.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+
+ function ComputeWorkgroup1x1TextureStore(ctx) {
+ const halfCanvasWidthStr = (ctx.canvas.width / 2).toFixed();
+ const halfCanvasHeightStr = (ctx.canvas.height / 2).toFixed();
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+@group(0) @binding(0) var outImage : texture_storage_2d<${format}, write>;
+
+@compute @workgroup_size(1, 1, 1)
+fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
+ var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ if (GlobalInvocationID.x < ${halfCanvasWidthStr}u) {
+ if (GlobalInvocationID.y < ${halfCanvasHeightStr}u) {
+ color.r = ${shaderValueStr};
+ } else {
+ color.b = ${shaderValueStr};
+ }
+ } else {
+ if (GlobalInvocationID.y < ${halfCanvasHeightStr}u) {
+ color.g = ${shaderValueStr};
+ } else {
+ color.r = ${shaderValueStr};
+ color.g = ${shaderValueStr};
+ }
+ }
+ textureStore(outImage, vec2<i32>(GlobalInvocationID.xy), color);
+ return;
+}
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const bg = t.device.createBindGroup({
+ entries: [{ binding: 0, resource: ctx.getCurrentTexture().createView() }],
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(ctx.canvas.width, ctx.canvas.height, 1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ function ComputeWorkgroup16x16TextureStore(ctx) {
+ const canvasWidthStr = ctx.canvas.width.toFixed();
+ const canvasHeightStr = ctx.canvas.height.toFixed();
+ const halfCanvasWidthStr = (ctx.canvas.width / 2).toFixed();
+ const halfCanvasHeightStr = (ctx.canvas.height / 2).toFixed();
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+@group(0) @binding(0) var outImage : texture_storage_2d<${format}, write>;
+
+@compute @workgroup_size(16, 16, 1)
+fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) {
+ if (GlobalInvocationID.x >= ${canvasWidthStr}u ||
+ GlobalInvocationID.y >= ${canvasHeightStr}u) {
+ return;
+ }
+ var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
+ if (GlobalInvocationID.x < ${halfCanvasWidthStr}u) {
+ if (GlobalInvocationID.y < ${halfCanvasHeightStr}u) {
+ color.r = ${shaderValueStr};
+ } else {
+ color.b = ${shaderValueStr};
+ }
+ } else {
+ if (GlobalInvocationID.y < ${halfCanvasHeightStr}u) {
+ color.g = ${shaderValueStr};
+ } else {
+ color.r = ${shaderValueStr};
+ color.g = ${shaderValueStr};
+ }
+ }
+ textureStore(outImage, vec2<i32>(GlobalInvocationID.xy), color);
+ return;
+}
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const bg = t.device.createBindGroup({
+ entries: [{ binding: 0, resource: ctx.getCurrentTexture().createView() }],
+ layout: pipeline.getBindGroupLayout(0)
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(
+ align(ctx.canvas.width, 16) / 16,
+ align(ctx.canvas.height, 16) / 16,
+ 1
+ );
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+ }
+
+ for (const { cvs, writeCanvasMethod } of targets) {
+ const ctx = cvs.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ let usage;
+ switch (writeCanvasMethod) {
+ case 'copyBufferToTexture':
+ case 'copyTextureToTexture':
+ usage = GPUTextureUsage.COPY_DST;
+ break;
+ case 'copyExternalImageToTexture':
+ usage = GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
+ break;
+ case 'DrawTextureSample':
+ case 'DrawVertexColor':
+ case 'DrawFragcoord':
+ usage = GPUTextureUsage.RENDER_ATTACHMENT;
+ break;
+ case 'FragmentTextureStore':
+ case 'ComputeWorkgroup1x1TextureStore':
+ case 'ComputeWorkgroup16x16TextureStore':
+ usage = GPUTextureUsage.STORAGE_BINDING;
+ break;
+ default:
+ unreachable();
+ }
+
+ ctx.configure({
+ device: t.device,
+ format,
+ usage
+ });
+
+ switch (writeCanvasMethod) {
+ case 'copyBufferToTexture':
+ copyBufferToTexture(ctx);
+ break;
+ case 'copyExternalImageToTexture':
+ await copyExternalImageToTexture(ctx);
+ break;
+ case 'copyTextureToTexture':
+ await copyTextureToTexture(ctx);
+ break;
+ case 'DrawTextureSample':
+ await DrawTextureSample(ctx);
+ break;
+ case 'DrawVertexColor':
+ DrawVertexColor(ctx);
+ break;
+ case 'DrawFragcoord':
+ DrawFragcoord(ctx);
+ break;
+ case 'FragmentTextureStore':
+ FragmentTextureStore(ctx);
+ break;
+ case 'ComputeWorkgroup1x1TextureStore':
+ ComputeWorkgroup1x1TextureStore(ctx);
+ break;
+ case 'ComputeWorkgroup16x16TextureStore':
+ ComputeWorkgroup16x16TextureStore(ctx);
+ break;
+ default:
+ unreachable();
+ }
+ }
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html
new file mode 100644
index 0000000000..d378bdfcf5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_copy.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_bgra8unorm_copy</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_copy_buffer_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_copy_texture_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_copy_external_image_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('bgra8unorm', [
+ { cvs: cvs_copy_buffer_to_texture, writeCanvasMethod: 'copyBufferToTexture' },
+ { cvs: cvs_copy_texture_to_texture, writeCanvasMethod: 'copyTextureToTexture' },
+ { cvs: cvs_copy_external_image_to_texture, writeCanvasMethod: 'copyExternalImageToTexture' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html
new file mode 100644
index 0000000000..99049e6e32
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_bgra8unorm_draw.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_bgra8unorm_draw</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_draw_texture_sample" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_draw_vertex_color" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_draw_fragcoord" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('bgra8unorm', [
+ { cvs: cvs_draw_texture_sample, writeCanvasMethod: 'DrawTextureSample' },
+ { cvs: cvs_draw_vertex_color, writeCanvasMethod: 'DrawVertexColor' },
+ { cvs: cvs_draw_fragcoord, writeCanvasMethod: 'DrawFragcoord' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_copy.https.html
new file mode 100644
index 0000000000..400afa121b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_copy.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_rgba16float_copy</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_copy_buffer_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_copy_texture_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_copy_external_image_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('rgba16float', [
+ { cvs: cvs_copy_buffer_to_texture, writeCanvasMethod: 'copyBufferToTexture' },
+ { cvs: cvs_copy_texture_to_texture, writeCanvasMethod: 'copyTextureToTexture' },
+ { cvs: cvs_copy_external_image_to_texture, writeCanvasMethod: 'copyExternalImageToTexture' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_draw.https.html
new file mode 100644
index 0000000000..a647fc2956
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_draw.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_rgba16float_draw</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_draw_texture_sample" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_draw_vertex_color" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_draw_fragcoord" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('rgba16float', [
+ { cvs: cvs_draw_texture_sample, writeCanvasMethod: 'DrawTextureSample' },
+ { cvs: cvs_draw_vertex_color, writeCanvasMethod: 'DrawVertexColor' },
+ { cvs: cvs_draw_fragcoord, writeCanvasMethod: 'DrawFragcoord' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_store.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_store.https.html
new file mode 100644
index 0000000000..b812129b0b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba16float_store.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_rgba16float_store</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_fragment_texture_store" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_compute_texture_store_1" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_compute_texture_store_2" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('rgba16float', [
+ { cvs: cvs_fragment_texture_store, writeCanvasMethod: 'FragmentTextureStore' },
+ { cvs: cvs_compute_texture_store_1, writeCanvasMethod: 'ComputeWorkgroup1x1TextureStore' },
+ { cvs: cvs_compute_texture_store_2, writeCanvasMethod: 'ComputeWorkgroup16x16TextureStore' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_copy.https.html
new file mode 100644
index 0000000000..d2570a3bdf
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_copy.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_rgba8unorm_copy</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_copy_buffer_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_copy_texture_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_copy_external_image_to_texture" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('rgba8unorm', [
+ { cvs: cvs_copy_buffer_to_texture, writeCanvasMethod: 'copyBufferToTexture' },
+ { cvs: cvs_copy_texture_to_texture, writeCanvasMethod: 'copyTextureToTexture' },
+ { cvs: cvs_copy_external_image_to_texture, writeCanvasMethod: 'copyExternalImageToTexture' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html
new file mode 100644
index 0000000000..647a829259
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_draw.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_rgba8unorm_draw</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_draw_texture_sample" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_draw_vertex_color" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_draw_fragcoord" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('rgba8unorm', [
+ { cvs: cvs_draw_texture_sample, writeCanvasMethod: 'DrawTextureSample' },
+ { cvs: cvs_draw_vertex_color, writeCanvasMethod: 'DrawVertexColor' },
+ { cvs: cvs_draw_fragcoord, writeCanvasMethod: 'DrawFragcoord' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html
new file mode 100644
index 0000000000..b82745658e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_complex_rgba8unorm_store.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_complex_rgba8unorm_store</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_complex-ref.html" />
+
+ <canvas id="cvs_fragment_texture_store" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_compute_texture_store_1" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs_compute_texture_store_2" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+
+ <script type="module">
+ import { run } from './canvas_complex.html.js';
+ run('rgba8unorm', [
+ { cvs: cvs_fragment_texture_store, writeCanvasMethod: 'FragmentTextureStore' },
+ { cvs: cvs_compute_texture_store_1, writeCanvasMethod: 'ComputeWorkgroup1x1TextureStore' },
+ { cvs: cvs_compute_texture_store_2, writeCanvasMethod: 'ComputeWorkgroup16x16TextureStore' },
+ ]);
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha.html.js
new file mode 100644
index 0000000000..81a4246906
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha.html.js
@@ -0,0 +1,177 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert, unreachable } from '../../../common/util/util.js';import { runRefTest } from './gpu_ref_test.js';
+
+
+
+export function run(
+format,
+alphaMode,
+writeCanvasMethod)
+{
+ runRefTest((t) => {
+ const module = t.device.createShaderModule({
+ code: `
+struct VertexOutput {
+@builtin(position) Position : vec4<f32>,
+@location(0) fragColor : vec4<f32>,
+}
+
+@vertex
+fn mainVS(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
+var pos = array<vec2<f32>, 6>(
+ vec2<f32>( 0.75, 0.75),
+ vec2<f32>( 0.75, -0.75),
+ vec2<f32>(-0.75, -0.75),
+ vec2<f32>( 0.75, 0.75),
+ vec2<f32>(-0.75, -0.75),
+ vec2<f32>(-0.75, 0.75));
+
+var offset = array<vec2<f32>, 4>(
+vec2<f32>( -0.25, 0.25),
+vec2<f32>( 0.25, 0.25),
+vec2<f32>(-0.25, -0.25),
+vec2<f32>( 0.25, -0.25));
+
+// Alpha channel value is set to 0.5 regardless of the canvas alpha mode.
+// For 'opaque' mode, it shouldn't affect the end result, as the alpha channel should always get cleared to 1.0.
+var color = array<vec4<f32>, 4>(
+ vec4<f32>(0.4, 0.0, 0.0, 0.5),
+ vec4<f32>(0.0, 0.4, 0.0, 0.5),
+ vec4<f32>(0.0, 0.0, 0.4, 0.5),
+ vec4<f32>(0.4, 0.4, 0.0, 0.5)); // 0.4 -> 0x66
+
+var output : VertexOutput;
+output.Position = vec4<f32>(pos[VertexIndex % 6u] + offset[VertexIndex / 6u], 0.0, 1.0);
+output.fragColor = color[VertexIndex / 6u];
+return output;
+}
+
+@fragment
+fn mainFS(@location(0) fragColor: vec4<f32>) -> @location(0) vec4<f32> {
+return fragColor;
+}
+ `
+ });
+
+ document.querySelectorAll('canvas').forEach((canvas) => {
+ const ctx = canvas.getContext('webgpu');
+ assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
+
+ switch (format) {
+ case 'bgra8unorm':
+ case 'bgra8unorm-srgb':
+ case 'rgba8unorm':
+ case 'rgba8unorm-srgb':
+ case 'rgba16float':
+ break;
+ default:
+ unreachable();
+ }
+
+ let usage = 0;
+ switch (writeCanvasMethod) {
+ case 'draw':
+ usage = GPUTextureUsage.RENDER_ATTACHMENT;
+ break;
+ case 'copy':
+ usage = GPUTextureUsage.COPY_DST;
+ break;
+ }
+ ctx.configure({
+ device: t.device,
+ format,
+ usage,
+ alphaMode
+ });
+
+ // The blending behavior here is to mimic 2d context blending behavior
+ // of drawing rects in order
+ // https://drafts.fxtf.org/compositing/#porterduffcompositingoperators_srcover
+ const kBlendStateSourceOver = {
+ color: {
+ srcFactor: 'src-alpha',
+ dstFactor: 'one-minus-src-alpha',
+ operation: 'add'
+ },
+ alpha: {
+ srcFactor: 'one',
+ dstFactor: 'one-minus-src-alpha',
+ operation: 'add'
+ }
+ };
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'mainVS'
+ },
+ fragment: {
+ module,
+ entryPoint: 'mainFS',
+ targets: [
+ {
+ format,
+ blend: { premultiplied: kBlendStateSourceOver, opaque: undefined }[alphaMode]
+ }]
+
+ },
+ primitive: {
+ topology: 'triangle-list'
+ }
+ });
+
+ let renderTarget;
+ switch (writeCanvasMethod) {
+ case 'draw':
+ renderTarget = ctx.getCurrentTexture();
+ break;
+ case 'copy':
+ renderTarget = t.device.createTexture({
+ size: [ctx.canvas.width, ctx.canvas.height],
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
+ });
+ break;
+ }
+ const renderPassDescriptor = {
+ colorAttachments: [
+ {
+ view: renderTarget.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ };
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
+ passEncoder.setPipeline(pipeline);
+ passEncoder.draw(6, 1, 0, 0);
+ passEncoder.draw(6, 1, 6, 0);
+ passEncoder.draw(6, 1, 12, 0);
+ passEncoder.draw(6, 1, 18, 0);
+ passEncoder.end();
+
+ switch (writeCanvasMethod) {
+ case 'draw':
+ break;
+ case 'copy':
+ commandEncoder.copyTextureToTexture(
+ {
+ texture: renderTarget
+ },
+ {
+ texture: ctx.getCurrentTexture()
+ },
+ [ctx.canvas.width, ctx.canvas.height]
+ );
+ break;
+ }
+
+ t.device.queue.submit([commandEncoder.finish()]);
+ });
+ });
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html
new file mode 100644
index 0000000000..60e8417c16
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_copy.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_bgra8unorm_opaque</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_composite_alpha_opaque-ref.html" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('bgra8unorm', 'opaque', 'copy');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html
new file mode 100644
index 0000000000..c0280a2a99
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_opaque_draw.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_bgra8unorm_opaque</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_composite_alpha_opaque-ref.html" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('bgra8unorm', 'opaque', 'draw');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
new file mode 100644
index 0000000000..70920dc0e6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_bgra8unorm_premultiplied</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ 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">
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('bgra8unorm', 'premultiplied', 'copy');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
new file mode 100644
index 0000000000..d12751fac2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_bgra8unorm_premultiplied</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ 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">
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('bgra8unorm', 'premultiplied', 'draw');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_copy.https.html
new file mode 100644
index 0000000000..4471f08480
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_copy.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba16float_opaque</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_composite_alpha_opaque-ref.html" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba16float', 'opaque', 'copy');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_draw.https.html
new file mode 100644
index 0000000000..11f0e73ec2
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_opaque_draw.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba16float_opaque</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_composite_alpha_opaque-ref.html" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba16float', 'opaque', 'draw');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
new file mode 100644
index 0000000000..ed722013c1
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba16float_premultiplied</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ 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">
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba16float', 'premultiplied', 'copy');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
new file mode 100644
index 0000000000..8a028b168e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba16float_premultiplied</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ 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">
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba16float', 'premultiplied', 'draw');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html
new file mode 100644
index 0000000000..7147631d19
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_copy.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba8unorm_opaque</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_composite_alpha_opaque-ref.html" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba8unorm', 'opaque', 'copy');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html
new file mode 100644
index 0000000000..ec2bb05ed3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_opaque_draw.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba8unorm_opaque</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
+ />
+ <link rel="match" href="./ref/canvas_composite_alpha_opaque-ref.html" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba8unorm', 'opaque', 'draw');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
new file mode 100644
index 0000000000..fa938aba41
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba8unorm_premultiplied</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ 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">
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba8unorm', 'premultiplied', 'copy');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
new file mode 100644
index 0000000000..b62e71054c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_composite_alpha_rgba8unorm_premultiplied</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta
+ name="assert"
+ 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">
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module">
+ import { run } from './canvas_composite_alpha.html.js';
+ run('rgba8unorm', 'premultiplied', 'draw');
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.html.js
new file mode 100644
index 0000000000..c1c2fcf975
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.html.js
@@ -0,0 +1,79 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { runRefTest } from './gpu_ref_test.js';runRefTest((t) => {
+ const device = t.device;
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
+
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 3>(
+ vec2(-1.0, 3.0),
+ vec2(-1.0,-1.0),
+ vec2( 3.0,-1.0)
+ );
+
+ return vec4(pos[VertexIndex], 0.0, 1.0);
+ }
+
+ @fragment fn fs(
+ @builtin(position) Pos : vec4<f32>
+ ) -> @location(0) vec4<f32> {
+ let black = vec4f(0, 0, 0, 1);
+ let white = vec4f(1, 1, 1, 1);
+ let iPos = vec4u(Pos);
+ let check = (iPos.x + iPos.y) & 1;
+ return mix(black, white, f32(check));
+ }
+ `
+ });
+
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: presentationFormat }]
+ }
+ });
+
+ function draw(selector, alphaMode) {
+ const canvas = document.querySelector(selector);
+ const context = canvas.getContext('webgpu');
+ context.configure({
+ device,
+ format: presentationFormat,
+ alphaMode
+ });
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: context.getCurrentTexture().createView(),
+ clearValue: [0.0, 0.0, 0.0, 0.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+
+ device.queue.submit([encoder.finish()]);
+ }
+
+ draw('#elem1', 'premultiplied');
+ draw('#elem2', 'premultiplied');
+ draw('#elem3', 'premultiplied');
+ draw('#elem4', 'opaque');
+ draw('#elem5', 'opaque');
+ draw('#elem6', 'opaque');
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html
new file mode 100644
index 0000000000..f51145645b
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/canvas_image_rendering.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU canvas_image_rendering</title>
+ <meta charset="utf-8" />
+ <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>
+ <script type="module" src="canvas_image_rendering.html.js"></script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/create-pattern-data-url.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/create-pattern-data-url.js
new file mode 100644
index 0000000000..dfb7690696
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/create-pattern-data-url.js
@@ -0,0 +1,23 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/ // creates a 4x4 pattern
+export default function createPatternDataURL() {const patternSize = 4;const ctx = document.createElement('canvas').getContext('2d');
+ ctx.canvas.width = patternSize;
+ ctx.canvas.height = patternSize;
+
+ const b = [0, 0, 0, 255];
+ const t = [0, 0, 0, 0];
+ const r = [255, 0, 0, 255];
+ const g = [0, 255, 0, 255];
+
+ const imageData = new ImageData(patternSize, patternSize);
+
+ imageData.data.set([
+ b, t, t, r,
+ t, b, g, t,
+ t, r, b, t,
+ g, t, t, b].
+ flat());
+ ctx.putImageData(imageData, 0, 0);
+ return { patternSize, imageData, dataURL: ctx.canvas.toDataURL() };
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/gpu_ref_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/gpu_ref_test.js
new file mode 100644
index 0000000000..ae7c29d98c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/gpu_ref_test.js
@@ -0,0 +1,26 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { assert } from '../../../common/util/util.js';import { takeScreenshotDelayed } from '../../../common/util/wpt_reftest_wait.js';
+
+
+
+
+
+export function runRefTest(fn) {
+ 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 queue = device.queue;
+
+ await fn({ device, queue });
+
+ takeScreenshotDelayed(50);
+ })();
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_clear-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_clear-ref.html
new file mode 100644
index 0000000000..e37b78c3a6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_clear-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU canvas_clear (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>
+ <canvas id="cvs1" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs2" 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 = '#66FF00';
+ ctx.fillRect(0, 0, c.width, c.height);
+ }
+
+ draw('cvs0');
+ draw('cvs1');
+ draw('cvs2');
+
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html
new file mode 100644
index 0000000000..a6da9f6748
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU canvas_colorspace (ref)</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <style>
+ canvas {
+ width: 128px;
+ height: 128px;
+ margin-right: 5px;
+ image-rendering: pixelated;
+ image-rendering: crisp-edges;
+ }
+ </style>
+ <body></body>
+ <script type="module" src="canvas_colorspace-ref.html.js"></script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html.js
new file mode 100644
index 0000000000..e87a1ba170
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_colorspace-ref.html.js
@@ -0,0 +1,41 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { kUnitCaseParamsBuilder } from '../../../../common/framework/params_builder.js';import { kCanvasAlphaModes, kCanvasColorSpaces } from '../../../capability_info.js';
+
+const kRGBAData = new Uint8Array([
+0, 255, 0, 255,
+117, 251, 7, 255,
+170, 35, 209, 255,
+80, 150, 200, 255]
+);
+const width = kRGBAData.length / 4;
+
+function createCanvas(colorSpace) {
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = 1;
+ const context = canvas.getContext('2d', {
+ colorSpace
+ });
+
+ const imgData = context.getImageData(0, 0, width, 1);
+ imgData.data.set(kRGBAData);
+ context.putImageData(imgData, 0, 0);
+
+ document.body.appendChild(canvas);
+}
+
+const u = kUnitCaseParamsBuilder.
+combine('alphaMode', kCanvasAlphaModes).
+combine('colorSpace', kCanvasColorSpaces).
+combine('creation', [
+'canvas',
+'transferControlToOffscreen',
+'transferControlToOffscreenWorker']
+);
+
+// Generate reference canvases for all combinations from the test.
+// We only need colorSpace to generate the correct reference.
+for (const { colorSpace } of u) {
+ createCanvas(colorSpace);
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_complex-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_complex-ref.html
new file mode 100644
index 0000000000..b1d46c108a
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_complex-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU canvas_complex (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>
+ <canvas id="cvs1" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="cvs2" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script>
+ function draw(ctx) {
+ ctx.fillStyle = '#660000';
+ ctx.fillRect(0, 0, 10, 10);
+ ctx.fillStyle = '#006600';
+ ctx.fillRect(10, 0, 10, 10);
+ ctx.fillStyle = '#000066';
+ ctx.fillRect(0, 10, 10, 10);
+ ctx.fillStyle = '#666600';
+ ctx.fillRect(10, 10, 10, 10);
+ }
+
+ draw(cvs0.getContext('2d'));
+ draw(cvs1.getContext('2d'));
+ draw(cvs2.getContext('2d'));
+
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_opaque-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_opaque-ref.html
new file mode 100644
index 0000000000..94b9486514
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_opaque-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU canvas_composite_alpha_premultiplied (ref)</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script>
+ document.querySelectorAll('canvas').forEach(canvas => {
+ const ctx = canvas.getContext('2d');
+ ctx.globalAlpha = 1.0;
+ ctx.fillStyle = '#660000';
+ ctx.fillRect(0, 0, 15, 15);
+ ctx.fillStyle = '#006600';
+ ctx.fillRect(5, 0, 15, 15);
+ ctx.fillStyle = '#000066';
+ ctx.fillRect(0, 5, 15, 20);
+ ctx.fillStyle = '#666600';
+ ctx.fillRect(5, 5, 20, 20);
+ });
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_premultiplied-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_premultiplied-ref.html
new file mode 100644
index 0000000000..635625ecc7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_composite_alpha_premultiplied-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU canvas_composite_alpha_premultiplied (ref)</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <style>
+ body { background-color: #F0E68C; }
+ #c-canvas { background-color: #8CF0E6; }
+ </style>
+ <canvas id="c-body" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <canvas id="c-canvas" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script>
+ document.querySelectorAll('canvas').forEach(canvas => {
+ const ctx = canvas.getContext('2d');
+ ctx.globalAlpha = 0.5;
+ ctx.fillStyle = '#660000';
+ ctx.fillRect(0, 0, 15, 15);
+ ctx.fillStyle = '#006600';
+ ctx.fillRect(5, 0, 15, 15);
+ ctx.fillStyle = '#000066';
+ ctx.fillRect(0, 5, 15, 20);
+ ctx.fillStyle = '#666600';
+ ctx.fillRect(5, 5, 20, 20);
+ });
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
new file mode 100644
index 0000000000..f9eca704e8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <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">
+ <script type="module">
+ import { takeScreenshotDelayed } from '../../../../common/util/wpt_reftest_wait.js';
+
+ (async () => {
+ const dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKdJREFUeF7t28kJwDAQA0Cp/6I3hJAqNK/kazDr2atJ7u7S9v3Z+76nnz18m7oBboAYIAZMRv//1fMKcAAHkCAJLuYAXoEv8ZMLcAAHcAAHcAAHjBZEOeBSDuAADuAADuAADtjrCqsIqQh98xAkSIIkSIIkSIIkSIKrYzJ6gyRIgiRIgiRIgiRIgiRoZ2hwYcp8gKqwSVF9AXuD9gbtDdobXGWw7nCbB5+MQQlHipKKAAAAAElFTkSuQmCC';
+ await Promise.all([...document.querySelectorAll('img')].map(img => {
+ img.src = dataURL;
+ return img.decode();
+ }));
+
+ takeScreenshotDelayed(50);
+ })();
+ </script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/resize_observer-ref.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/resize_observer-ref.html
new file mode 100644
index 0000000000..5259a25c27
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/ref/resize_observer-ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU ResizeObserver test (ref)</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <style>
+ .outer {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ }
+ .outer>* {
+ display: block;
+ height: 100px;
+ }
+ </style>
+ <body>
+ <div id="dpr"></div>
+ <div class="outer"></div>
+ <script type="module">
+ import { takeScreenshotDelayed } from '../../../../common/util/wpt_reftest_wait.js';
+ import createPatternDataURL from '../create-pattern-data-url.js';
+
+ (async () => {
+ const {patternSize, dataURL} = createPatternDataURL();
+
+ document.querySelector('#dpr').textContent = `dpr: ${devicePixelRatio}`;
+
+ /**
+ * Set the pattern's size on this element so that it draws where
+ * 1 pixel in the pattern maps to 1 devicePixel.
+ */
+ function setPattern(elem) {
+ const oneDevicePixel = 1 / devicePixelRatio;
+ const patternPixels = oneDevicePixel * patternSize;
+ elem.style.backgroundImage = `url("${dataURL}")`;
+ elem.style.backgroundSize = `${patternPixels}px ${patternPixels}px`;
+ }
+
+ /*
+ This ref creates elements like this
+ <body>
+ <div class="outer">
+ <div></div>
+ <div></div>
+ <div></div>
+ ...
+ </div>
+ </body>
+ Where the outer div is a flexbox centering the child elements.
+ Each of the child elements is set to a different width in percent.
+ The devicePixelContentBox size of each child element is observed
+ with a ResizeObserver and when changed, a pattern is applied to
+ the element and the pattern's size set so each pixel in the pattern
+ will be one device pixel.
+ A similar process happens in the test HTML using canvases
+ and patterns generated using putImageData.
+ The test and this reference page should then match.
+ */
+
+ const outerElem = document.querySelector('.outer');
+
+ let resolve;
+ const promise = new Promise(_resolve => (resolve = _resolve));
+
+ /**
+ * Set the pattern's size on this element so that it draws where
+ * 1 pixel in the pattern maps to 1 devicePixel.
+ */
+ function setPatterns(entries) {
+ for (const entry of entries) {
+ setPattern(entry.target)
+ }
+ resolve();
+ }
+
+ const observer = new ResizeObserver(setPatterns);
+ for (let percentSize = 7; percentSize < 100; percentSize += 13) {
+ const innerElem = document.createElement('div');
+ innerElem.style.width = `${percentSize}%`;
+ observer.observe(innerElem, {box:"device-pixel-content-box"});
+ outerElem.appendChild(innerElem);
+ }
+
+ await promise;
+ takeScreenshotDelayed(50);
+ })();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.html.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.html.js
new file mode 100644
index 0000000000..3e5bb3b4ae
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.html.js
@@ -0,0 +1,150 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import createPatternDataURL from './create-pattern-data-url.js';import { runRefTest } from './gpu_ref_test.js';
+runRefTest(async (t) => {
+ const { patternSize, imageData: patternImageData } = createPatternDataURL();
+
+ document.querySelector('#dpr').textContent = `dpr: ${devicePixelRatio}`;
+
+ const device = t.device;
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
+
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs(
+ @builtin(vertex_index) VertexIndex : u32
+ ) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 3>(
+ vec2(-1.0, 3.0),
+ vec2(-1.0,-1.0),
+ vec2( 3.0,-1.0)
+ );
+
+ return vec4(pos[VertexIndex], 0.0, 1.0);
+ }
+
+ @group(0) @binding(0) var pattern: texture_2d<f32>;
+
+ @fragment fn fs(
+ @builtin(position) Pos : vec4<f32>
+ ) -> @location(0) vec4<f32> {
+ let patternSize = textureDimensions(pattern, 0);
+ let uPos = vec2u(Pos.xy) % patternSize;
+ return textureLoad(pattern, uPos, 0);
+ }
+ `
+ });
+
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs'
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: presentationFormat }]
+ }
+ });
+
+ const tex = device.createTexture({
+ size: [patternSize, patternSize, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
+ });
+ device.queue.writeTexture(
+ { texture: tex },
+ patternImageData.data,
+ { bytesPerRow: patternSize * 4, rowsPerImage: 4 },
+ { width: patternSize, height: patternSize }
+ );
+
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: tex.createView() }]
+ });
+
+ function setCanvasPattern(
+ canvas,
+ devicePixelWidth,
+ devicePixelHeight)
+ {
+ canvas.width = devicePixelWidth;
+ canvas.height = devicePixelHeight;
+
+ const context = canvas.getContext('webgpu');
+ context.configure({
+ device,
+ format: presentationFormat,
+ alphaMode: 'premultiplied'
+ });
+
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: context.getCurrentTexture().createView(),
+ clearValue: [0.0, 0.0, 0.0, 0.0],
+ loadOp: 'clear',
+ storeOp: 'store'
+ }]
+
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.draw(3);
+ pass.end();
+
+ device.queue.submit([encoder.finish()]);
+ }
+
+ /*
+ This test creates elements like this
+ <body>
+ <div class="outer">
+ <canvas></canvas>
+ <canvas></canvas>
+ <canvas></canvas>
+ ...
+ </div>
+ </body>
+ Where the outer div is a flexbox centering the child canvases.
+ Each of the child canvases is set to a different width in percent.
+ The size of each canvas in device pixels is queried with ResizeObserver
+ and then each canvases' resolution is set to that size so that there should
+ be one pixel in each canvas for each device pixel.
+ Each canvas is filled with a pattern using putImageData.
+ In the reference the canvas elements are replaced with divs.
+ For the divs the same pattern is applied with CSS and its size
+ adjusted so the pattern should appear with one pixel in the pattern
+ corresponding to 1 device pixel.
+ The reference and this page should then match.
+ */
+
+ const outerElem = document.querySelector('.outer');
+
+ let resolve;
+ const promise = new Promise((_resolve) => resolve = _resolve);
+
+ function setPatternsUsingSizeInfo(entries) {
+ for (const entry of entries) {
+ setCanvasPattern(
+ entry.target,
+ entry.devicePixelContentBoxSize[0].inlineSize,
+ entry.devicePixelContentBoxSize[0].blockSize
+ );
+ }
+ resolve(true);
+ }
+
+ const observer = new ResizeObserver(setPatternsUsingSizeInfo);
+ for (let percentSize = 7; percentSize < 100; percentSize += 13) {
+ const canvasElem = document.createElement('canvas');
+ canvasElem.style.width = `${percentSize}%`;
+ observer.observe(canvasElem, { box: 'device-pixel-content-box' });
+ outerElem.appendChild(canvasElem);
+ }
+
+ await promise;
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.https.html b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.https.html
new file mode 100644
index 0000000000..2845cc29eb
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/reftests/resize_observer.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU resize_observer</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta name="assert" content="WebGPU canvases should return the correct ResizeObserver values" />
+ <link rel="match" href="./ref/resize_observer-ref.html" />
+ <style>
+ .outer {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ }
+ .outer>* {
+ display: block;
+ height: 100px;
+ }
+ </style>
+ <body>
+ <div id="dpr"></div>
+ <div class="outer"></div>
+ <script type="module" src="resize_observer.html.js"></script>
+ </body>
+</html>
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/util.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/util.js
new file mode 100644
index 0000000000..f34723510c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/util.js
@@ -0,0 +1,307 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { SkipTestCase } from '../../common/framework/fixture.js';import { getResourcePath } from '../../common/framework/resources.js';import { makeTable } from '../../common/util/data_tables.js';
+import { timeout } from '../../common/util/timeout.js';
+import { ErrorWithExtra, raceWithRejectOnTimeout } from '../../common/util/util.js';
+
+
+
+
+
+
+
+
+
+
+export const kVideoInfo =
+makeTable(
+ ['mimeType'],
+ [undefined], {
+ // 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']
+ });
+
+
+// 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) {
+ const unorm8 = new Uint8ClampedArray(expectation.length);
+
+ for (let i = 0; i < expectation.length; ++i) {
+ unorm8[i] = Math.round(expectation[i] * 255.0);
+ }
+
+ return new Uint8Array(unorm8.buffer);
+}
+
+// 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
+},
+{
+ 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])
+}];
+
+
+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
+}];
+
+
+/**
+ * Starts playing a video and waits for it to be consumable.
+ * Returns a promise which resolves after `callback` (which may be async) completes.
+ *
+ * @param video An HTML5 Video element.
+ * @param callback Function to call when video is ready.
+ *
+ * Adapted from https://github.com/KhronosGroup/WebGL/blob/main/sdk/tests/js/webgl-test-utils.js
+ */
+export function startPlayingAndWaitForVideo(
+video,
+callback)
+{
+ return raceWithRejectOnTimeout(
+ new Promise((resolve, reject) => {
+ const callbackAndResolve = () =>
+ void (async () => {
+ try {
+ await callback();
+ resolve();
+ } catch (ex) {
+ reject(ex);
+ }
+ })();
+ if (video.error) {
+ reject(
+ new ErrorWithExtra('Video.error: ' + video.error.message, () => ({ error: video.error }))
+ );
+ return;
+ }
+
+ video.addEventListener(
+ 'error',
+ (event) => reject(new ErrorWithExtra('Video received "error" event', () => ({ event }))),
+ true
+ );
+
+ if (video.requestVideoFrameCallback) {
+ video.requestVideoFrameCallback(() => {
+ callbackAndResolve();
+ });
+ } else {
+ // If requestVideoFrameCallback isn't available, check each frame if the video has advanced.
+ const timeWatcher = () => {
+ if (video.currentTime > 0) {
+ callbackAndResolve();
+ } else {
+ requestAnimationFrame(timeWatcher);
+ }
+ };
+ timeWatcher();
+ }
+
+ video.loop = true;
+ video.muted = true;
+ video.preload = 'auto';
+ video.play().catch(reject);
+ }),
+ 2000,
+ 'Video never became ready'
+ );
+}
+
+/**
+ * Fire a `callback` when the script animation reaches a new frame.
+ * Returns a promise which resolves after `callback` (which may be async) completes.
+ */
+export function waitForNextTask(callback) {
+ const { promise, callbackAndResolve } = callbackHelper(callback, 'wait for next task timed out');
+ timeout(() => {
+ callbackAndResolve();
+ }, 0);
+
+ return promise;
+}
+
+/**
+ * Fire a `callback` when the video reaches a new frame.
+ * Returns a promise which resolves after `callback` (which may be async) completes.
+ *
+ * MAINTENANCE_TODO: Find a way to implement this for browsers without requestVideoFrameCallback as
+ * well, similar to the timeWatcher path in startPlayingAndWaitForVideo. If that path is proven to
+ * work well, we can consider getting rid of the requestVideoFrameCallback path.
+ */
+export function waitForNextFrame(
+video,
+callback)
+{
+ const { promise, callbackAndResolve } = callbackHelper(callback, 'waitForNextFrame timed out');
+
+ if ('requestVideoFrameCallback' in video) {
+ video.requestVideoFrameCallback(() => {
+ callbackAndResolve();
+ });
+ } else {
+ throw new SkipTestCase('waitForNextFrame currently requires requestVideoFrameCallback');
+ }
+
+ return promise;
+}
+
+export async function getVideoFrameFromVideoElement(
+test,
+video)
+{
+ if (video.captureStream === undefined) {
+ test.skip('HTMLVideoElement.captureStream is not supported');
+ }
+
+ return raceWithRejectOnTimeout(
+ new Promise((resolve) => {
+ const videoTrack = video.captureStream().getVideoTracks()[0];
+ const trackProcessor = new MediaStreamTrackProcessor({
+ track: videoTrack
+ });
+ const transformer = new TransformStream({
+ transform(videoFrame, _controller) {
+ videoTrack.stop();
+ resolve(videoFrame);
+ },
+ flush(controller) {
+ controller.terminate();
+ }
+ });
+ const trackGenerator = new MediaStreamTrackGenerator({
+ kind: 'video'
+ });
+ trackProcessor.readable.
+ pipeThrough(transformer).
+ pipeTo(trackGenerator.writable).
+ catch(() => {});
+ }),
+ 2000,
+ 'Video never became ready'
+ );
+}
+
+/**
+ * Create HTMLVideoElement based on VideoName. Check whether video is playable in current
+ * browser environment.
+ * Returns a HTMLVideoElement.
+ *
+ * @param t: GPUTest that requires getting HTMLVideoElement
+ * @param videoName: Required video name
+ *
+ */
+export function getVideoElement(t, videoName) {
+ const videoElement = document.createElement('video');
+ const videoInfo = kVideoInfo[videoName];
+
+ if (videoElement.canPlayType(videoInfo.mimeType) === '') {
+ t.skip('Video codec is not supported');
+ }
+
+ const videoUrl = getResourcePath(videoName);
+ videoElement.src = videoUrl;
+
+ return videoElement;
+}
+
+/**
+ * Helper for doing something inside of a (possibly async) callback (directly, not in a following
+ * microtask), and returning a promise when the callback is done.
+ * MAINTENANCE_TODO: Use this in startPlayingAndWaitForVideo (and make sure it works).
+ */
+function callbackHelper(
+callback,
+timeoutMessage)
+{
+ let callbackAndResolve;
+
+ const promiseWithoutTimeout = new Promise((resolve, reject) => {
+ callbackAndResolve = () =>
+ void (async () => {
+ try {
+ await callback(); // catches both exceptions and rejections
+ resolve();
+ } catch (ex) {
+ reject(ex);
+ }
+ })();
+ });
+ const promise = raceWithRejectOnTimeout(promiseWithoutTimeout, 2000, timeoutMessage);
+ return { promise, callbackAndResolve: callbackAndResolve };
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.js
new file mode 100644
index 0000000000..8eff1ae8cd
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.js
@@ -0,0 +1,83 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { getGPU, setDefaultRequestAdapterOptions } from '../../../common/util/navigator_gpu.js';import { assert, objectEquals, iterRange } from '../../../common/util/util.js';
+async function basicTest() {
+ const adapter = await getGPU(null).requestAdapter();
+ assert(adapter !== null, 'Failed to get adapter.');
+
+ const device = await adapter.requestDevice();
+ assert(device !== null, 'Failed to get device.');
+
+ const kOffset = 1230000;
+ const pipeline = device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: device.createShaderModule({
+ code: `
+ struct Buffer { data: array<u32>, };
+
+ @group(0) @binding(0) var<storage, read_write> buffer: Buffer;
+ @compute @workgroup_size(1u) fn main(
+ @builtin(global_invocation_id) id: vec3<u32>) {
+ buffer.data[id.x] = id.x + ${kOffset}u;
+ }
+ `
+ }),
+ entryPoint: 'main'
+ }
+ });
+
+ const kNumElements = 64;
+ const kBufferSize = kNumElements * 4;
+ const buffer = device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ });
+
+ const resultBuffer = device.createBuffer({
+ size: kBufferSize,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer } }]
+ });
+
+ const encoder = device.createCommandEncoder();
+
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(kNumElements);
+ pass.end();
+
+ encoder.copyBufferToBuffer(buffer, 0, resultBuffer, 0, kBufferSize);
+
+ device.queue.submit([encoder.finish()]);
+
+ const expected = new Uint32Array([...iterRange(kNumElements, (x) => x + kOffset)]);
+
+ await resultBuffer.mapAsync(GPUMapMode.READ);
+ const actual = new Uint32Array(resultBuffer.getMappedRange());
+
+ assert(objectEquals(actual, expected), 'compute pipeline ran');
+
+ resultBuffer.destroy();
+ buffer.destroy();
+ device.destroy();
+}
+
+self.onmessage = async (ev) => {
+ const defaultRequestAdapterOptions =
+ ev.data.defaultRequestAdapterOptions;
+ setDefaultRequestAdapterOptions(defaultRequestAdapterOptions);
+
+ let error = undefined;
+ try {
+ await basicTest();
+ } catch (err) {
+ error = err.toString();
+ }
+ self.postMessage({ error });
+}; \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.spec.js
new file mode 100644
index 0000000000..56d07640e4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker.spec.js
@@ -0,0 +1,35 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+Tests WebGPU is available in a 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.
+`;import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert } from '../../../common/util/util.js';
+
+export const g = makeTestGroup(Fixture);
+
+function isNode() {
+ return typeof process !== 'undefined' && process?.versions?.node !== undefined;
+}
+
+g.test('worker').
+desc(`test WebGPU is available in DedicatedWorkers 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.
+ const url = './worker_launcher.js';
+ const { launchWorker } = await import(url);
+ const result = await launchWorker();
+ assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
+}); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker_launcher.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker_launcher.js
new file mode 100644
index 0000000000..11c4a4e7e3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/web_platform/worker/worker_launcher.js
@@ -0,0 +1,18 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/import { getDefaultRequestAdapterOptions } from '../../../common/util/navigator_gpu.js';
+
+
+
+export async function launchWorker() {
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/worker.js';
+ const worker = new Worker(workerPath, { type: 'module' });
+
+ const promise = new Promise((resolve) => {
+ worker.addEventListener('message', (ev) => resolve(ev.data), { once: true });
+ });
+ worker.postMessage({ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions() });
+ return await promise;
+} \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/websockets/bug1793935.any.js b/testing/web-platform/mozilla/tests/websockets/bug1793935.any.js
new file mode 100644
index 0000000000..4ef84b3da4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/websockets/bug1793935.any.js
@@ -0,0 +1,23 @@
+// The module has an import that's immediately called by the start function.
+const module = new Uint8Array([
+ 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96,
+ 0, 0, 2, 7, 1, 1, 97, 1, 98, 0, 0, 3,
+ 2, 1, 0, 8, 1, 1, 10, 6, 1, 4, 0, 16,
+ 0, 11, 0, 36, 16, 115, 111, 117, 114, 99, 101, 77,
+ 97, 112, 112, 105, 110, 103, 85, 82, 76, 18, 46, 47,
+ 114, 101, 108, 101, 97, 115, 101, 46, 119, 97, 115, 109,
+ 46, 109, 97, 112
+]);
+
+// The WebSocket server does not need to exist, because the bug occurs before
+// any connection is attempted.
+const imports = {
+ a: {
+ b: Reflect.construct.bind(null, WebSocket, ["ws://localhost:1234"])
+ }
+};
+
+promise_test(
+ () => WebAssembly.instantiate(module, imports),
+ "Ensure WebSockets can be constructed from WebAssembly"
+); \ No newline at end of file
diff --git a/testing/web-platform/mozilla/tests/webtransport/bfcache/closed.tentative.https.html b/testing/web-platform/mozilla/tests/webtransport/bfcache/closed.tentative.https.html
new file mode 100644
index 0000000000..8fbf27922d
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webtransport/bfcache/closed.tentative.https.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>WebTransport API: bfcache closed</title>
+<link rel=help href="https://w3c.github.io/webtransport/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script type="module">
+
+import { runWebTransportBfcacheTest } from "./helpers.js";
+import { webtransport_url } from "./ports.sub.js";
+import { worker_function } from "./worker.js";
+
+const id = token();
+const url = webtransport_url(`client-close.py?token=${id}`);
+
+// We can't load ./worker.js in runWebTransportBfcacheTest, because it uses
+// execute_script, and so the baseurl is helper.sub.js's baseurl.
+// We also can't pass a worker to it. So, load the worker as a string, and pass
+// the string and turn it into a worker via a Blob. Fun!
+const worker_string = worker_function.toString().replace(/^async function .+\{?|\}$/g, '');
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (url) => {
+ window.wt = new WebTransport(url);
+ await window.wt.ready;
+ window.wt.close();
+ await window.wt.closed;
+ },
+ argsBeforeNavigation: [url],
+ shouldBeCached: true
+}, "A closed WebTransport on MainThread must allow bfcache");
+
+// we can't have runWebTransportBfcacheTest use [scripts] to load the helper.js
+// script into the test, due to the same baseurl issue, so just do this inline
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new SharedWorker(URL.createObjectURL(workerBlob));
+ worker.port.start();
+ await postToWorkerAndWait(worker.port, { op: "openandclose", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: true
+}, "A closed WebTransport in a shared worker must allow bfcache");
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new Worker(URL.createObjectURL(workerBlob));
+ await postToWorkerAndWait(window.worker, { op: "open", url: url });
+ await postToWorkerAndWait(window.worker, { op: "close" });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: true
+}, "A closed WebTransport in a worker must allow bfcache");
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new Worker(URL.createObjectURL(workerBlob));
+ await postToWorkerAndWait(window.worker, { op: "open", url: url });
+ await postToWorkerAndWait(window.worker, { op: "close" });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: true
+}, "A closed WebTransport in a nested worker must allow bfcache");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/webtransport/bfcache/connected.tentative.https.html b/testing/web-platform/mozilla/tests/webtransport/bfcache/connected.tentative.https.html
new file mode 100644
index 0000000000..de2a2fc7a7
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webtransport/bfcache/connected.tentative.https.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>WebTransport API: bfcache connected</title>
+<link rel=help href="https://w3c.github.io/webtransport/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script type="module">
+
+import { runWebTransportBfcacheTest } from "./helpers.js";
+import { webtransport_url } from "./ports.sub.js";
+import { worker_function } from "./worker.js";
+
+const id = token();
+const url = webtransport_url(`client-close.py?token=${id}`);
+
+// We can't load ./worker.js in runWebTransportBfcacheTest, because it uses
+// execute_script, and so the baseurl is helper.sub.js's baseurl, which is
+// the main wpt test directory.
+// We also can't pass a worker to it. So, load the worker as a string, and pass
+// the string and turn it into a worker via a Blob. Fun!
+const worker_string = worker_function.toString().replace(/^async function .+\{?|\}$/g, '');
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (url) => {
+ window.wt = new WebTransport(url);
+ await window.wt.ready;
+ },
+ argsBeforeNavigation: [url],
+ shouldBeCached: false
+}, "A connected WebTransport on MainThread must prevent bfcache");
+
+// we can't have runWebTransportBfcacheTest use [scripts] to load the helper.js
+// script into the test, due to the same baseurl issue, so just do this inline
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new SharedWorker(URL.createObjectURL(workerBlob));
+ worker.port.start();
+ await postToWorkerAndWait(worker.port, { op: "open", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: false
+}, "A connected WebTransport in a shared worker must prevent bfcache");
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new Worker(URL.createObjectURL(workerBlob));
+ await postToWorkerAndWait(window.worker, { op: "open", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: false
+}, "A connected WebTransport in a worker must prevent bfcache");
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new Worker(URL.createObjectURL(workerBlob));
+ await postToWorkerAndWait(window.worker, { op: "open", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: false
+}, "A connected WebTransport in a nested worker must prevent bfcache");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/webtransport/bfcache/failed.tentative.https.html b/testing/web-platform/mozilla/tests/webtransport/bfcache/failed.tentative.https.html
new file mode 100644
index 0000000000..94facfa6a8
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webtransport/bfcache/failed.tentative.https.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>WebTransport API: bfcache failed</title>
+<link rel=help href="https://w3c.github.io/webtransport/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/html/browsers/browsing-the-web/back-forward-cache/resources/helper.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script type="module">
+
+import { runWebTransportBfcacheTest } from "./helpers.js";
+import { webtransport_url } from "./ports.sub.js";
+import { worker_function } from "./worker.js";
+
+const id = token();
+const url = webtransport_url('custom-response.py?:status=404');
+
+// We can't load ./worker.js in runWebTransportBfcacheTest, because it uses
+// execute_script, and so the baseurl is helper.sub.js's baseurl.
+// We also can't pass a worker to it. So, load the worker as a string, and pass
+// the string and turn it into a worker via a Blob. Fun!
+const worker_string = worker_function.toString().replace(/^async function .+\{?|\}$/g, '');
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (url) => {
+ // make sure it isn't immediately freed
+ try {
+ window.wt = new WebTransport(url);
+ await window.wt.ready;
+ assert_unreached('Opened invalid URL');
+ } catch(e) {
+ }
+ },
+ argsBeforeNavigation: [url],
+ shouldBeCached: true
+}, "A failed WebTransport on MainThread must allow bfcache");
+
+// we can't have runWebTransportBfcacheTest use [scripts] to load the helper.js
+// script into the test, due to the same baseurl issue, so just do this inline
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new SharedWorker(URL.createObjectURL(workerBlob));
+ worker.port.start();
+ await postToWorkerAndWait(worker.port, { op: "open", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: true
+}, "A failed WebTransport in a shared worker must allow bfcache");
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new Worker(URL.createObjectURL(workerBlob));
+ await postToWorkerAndWait(window.worker, { op: "open", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: true
+}, "A failed WebTransport in a worker must allow bfcache");
+
+runWebTransportBfcacheTest({
+ funcBeforeNavigation: async (worker_string, url) => {
+ let next_request_id = 0;
+ function postToWorkerAndWait(worker, data) {
+ return new Promise(resolve => {
+ data.rqid = next_request_id++;
+ worker.postMessage(data);
+ const listener = event => {
+ if (event.data.rqid !== data.rqid)
+ return;
+ worker.removeEventListener('message', listener);
+ resolve(event.data);
+ };
+ worker.addEventListener('message', listener);
+ });
+ };
+
+ let workerBlob = new Blob([worker_string], { type:'text/javascript' });
+ window.worker = new Worker(URL.createObjectURL(workerBlob));
+ await postToWorkerAndWait(window.worker, { op: "open", url: url });
+ },
+ argsBeforeNavigation: [worker_string, url],
+ shouldBeCached: true
+}, "A failed WebTransport in a nested worker must allow bfcache");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/webtransport/bfcache/helpers.js b/testing/web-platform/mozilla/tests/webtransport/bfcache/helpers.js
new file mode 100644
index 0000000000..d8ab9b62d3
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webtransport/bfcache/helpers.js
@@ -0,0 +1,17 @@
+export function runWebTransportBfcacheTest(params, description) {
+ runBfcacheTest(
+ {
+ // due to the baseurl issue, this would try to load the scripts from
+ // the main wpt test directory
+ // scripts: ["/webtransport/resources/helpers.js"],
+ openFunc: url =>
+ window.open(
+ url + `&prefix=${location.pathname}-${description}`,
+ "_blank",
+ "noopener"
+ ),
+ ...params,
+ },
+ description
+ );
+}
diff --git a/testing/web-platform/mozilla/tests/webtransport/bfcache/ports.sub.js b/testing/web-platform/mozilla/tests/webtransport/bfcache/ports.sub.js
new file mode 100644
index 0000000000..cb4262c9f4
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webtransport/bfcache/ports.sub.js
@@ -0,0 +1,12 @@
+// The file including this must also include /common/get-host-info.sub.js to
+// pick up the necessary constants.
+
+const HOST = get_host_info().ORIGINAL_HOST;
+const PORT = '{{ports[webtransport-h3][0]}}';
+const BASE = `https://${HOST}:${PORT}`;
+
+// Create URL for WebTransport session.
+export function webtransport_url(handler) {
+ return `${BASE}/webtransport/handlers/${handler}`;
+}
+
diff --git a/testing/web-platform/mozilla/tests/webtransport/bfcache/worker.js b/testing/web-platform/mozilla/tests/webtransport/bfcache/worker.js
new file mode 100644
index 0000000000..b0eb890f42
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webtransport/bfcache/worker.js
@@ -0,0 +1,50 @@
+export async function worker_function() {
+
+let wt = null;
+
+async function processMessage(e) {
+ const target = this;
+
+ function respond(data) {
+ target.postMessage(Object.assign(data, {rqid: e.data.rqid}));
+ }
+
+ // ORB will block errors (webtransport_url('custom-response.py?:status=404');)
+ // so we need to try/catch
+ try {
+ switch (e.data.op) {
+ case 'open': {
+ wt = new WebTransport(e.data.url);
+ await wt.ready;
+ respond({ack: 'open'});
+ break;
+ }
+
+ case 'openandclose': {
+ wt = new WebTransport(e.data.url);
+ await wt.ready;
+ wt.close();
+ await wt.closed;
+ respond({ack: 'openandclose'});
+ break;
+ }
+
+ case 'close': {
+ wt.close();
+ await wt.closed;
+ respond({ack: 'close'});
+ break;
+ }
+ }
+ } catch(e) {
+ respond({failed: true});
+ }
+}
+
+self.addEventListener('message', processMessage);
+
+self.addEventListener('connect', ev => {
+ // Shared worker case
+ ev.ports[0].onmessage = processMessage;
+});
+}
diff --git a/testing/web-platform/mozilla/tests/workers/2-mib-file.py b/testing/web-platform/mozilla/tests/workers/2-mib-file.py
new file mode 100644
index 0000000000..cfb563ff21
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/2-mib-file.py
@@ -0,0 +1,7 @@
+import random
+import string
+
+
+def main(request, response):
+ r = "".join(random.choice(string.ascii_letters) for _ in range(2 * 1024 * 1024))
+ return r
diff --git a/testing/web-platform/mozilla/tests/workers/bug1674278-crash.html b/testing/web-platform/mozilla/tests/workers/bug1674278-crash.html
new file mode 100644
index 0000000000..2b037e5b37
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/bug1674278-crash.html
@@ -0,0 +1,6 @@
+<html class='test-wait'>
+<script>
+var worker = new Worker('bug1674278.js');
+worker.postMessage('', []);
+</script>
+</html>
diff --git a/testing/web-platform/mozilla/tests/workers/bug1674278.js b/testing/web-platform/mozilla/tests/workers/bug1674278.js
new file mode 100644
index 0000000000..56105cb76e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/bug1674278.js
@@ -0,0 +1,6 @@
+self.onmessage = async function(e) {
+ var a = await self.fetch('2-mib-file.py');
+ var b = await a.blob();
+ self.close()
+ await b.arrayBuffer();
+}
diff --git a/testing/web-platform/mozilla/tests/workers/modules/dedicated-worker-import-csp.html b/testing/web-platform/mozilla/tests/workers/modules/dedicated-worker-import-csp.html
new file mode 100644
index 0000000000..ed38ecb3e5
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/modules/dedicated-worker-import-csp.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: CSP for ES Modules</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+async function openWindow(url) {
+ const win = window.open(url, '_blank');
+ add_result_callback(() => win.close());
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msg_event.data, 'LOADED');
+ return win;
+}
+
+function import_csp_test(
+ cspHeader, importType, expectedImportedModules, description) {
+ // Append CSP header to windowURL for static import tests since static import
+ // scripts should obey Window's CSP.
+ const windowURL = `resources/new-worker-window.html`;
+ // Append CSP header to scriptURL for dynamic import tests since dynamic
+ // import scripts should obey Worker script's response's CSP.
+ const scriptURL = `${importType}-import-remote-origin-script-worker.sub.js` +
+ `?pipe=header(Content-Security-Policy, ${cspHeader})`;
+ promise_test(async () => {
+ const win = await openWindow(windowURL);
+ // Ask the window to start a dedicated worker.
+ win.postMessage(scriptURL, '*');
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_array_equals(msg_event.data, expectedImportedModules);
+ }, description);
+}
+
+// Tests for static import.
+//
+// Static import should obey the worker-src directive and the script-src
+// directive. If the both directives are specified, the worker-src directive
+// should be prioritized.
+//
+// Step 1: "If the result of executing 6.6.1.11 Get the effective directive for
+// request on request is "worker-src", and policy contains a directive whose
+// name is "worker-src", return "Allowed"."
+// "Note: If worker-src is present, we’ll defer to it when handling worker
+// requests."
+// https://w3c.github.io/webappsec-csp/#script-src-pre-request
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'",
+ "static",
+ ['ERROR'],
+ "worker-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "worker-src * 'unsafe-inline'",
+ "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should allow cross origin static import.")
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'",
+ "static",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'",
+ "static",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin static import.")
+
+import_csp_test(
+ "worker-src *; script-src 'self' 'unsafe-inline'",
+ "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should override script-src 'self' directive and " +
+ "allow cross origin static import.");
+
+import_csp_test(
+ "worker-src 'self'; script-src * 'unsafe-inline'",
+ "static",
+ ['ERROR'],
+ "worker-src 'self' directive should override script-src * directive and " +
+ "disallow cross origin static import.");
+
+// Tests for dynamic import.
+//
+// Dynamic import should obey the script-src directive instead of the worker-src
+// directive according to the specs:
+//
+// Dynamic import has the "script" destination.
+// Step 2.4: "Fetch a module script graph given url, ..., "script", ..."
+// https://html.spec.whatwg.org/multipage/webappapis.html#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)
+//
+// The "script" destination should obey the script-src CSP directive.
+// Step 2: "If request's destination is script-like:"
+// https://w3c.github.io/webappsec-csp/#script-src-pre-request
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'",
+ "dynamic",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin dynamic import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'",
+ "dynamic",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin dynamic import.")
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'",
+ "dynamic",
+ ["export-on-load-script.js"],
+ "worker-src 'self' directive should not take effect on dynamic import.");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js b/testing/web-platform/mozilla/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js
new file mode 100644
index 0000000000..7ed6543890
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/modules/resources/dynamic-import-remote-origin-script-worker.sub.js
@@ -0,0 +1,17 @@
+// Import a remote origin script.
+const importUrl =
+ 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.js';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ import(importUrl)
+ .then(module => postMessage(module.importedModules))
+ .catch(e => postMessage(['ERROR']));
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ onconnect = e => {
+ import(importUrl)
+ .then(module => e.ports[0].postMessage(module.importedModules))
+ .catch(error => e.ports[0].postMessage(['ERROR']));
+ };
+}
diff --git a/testing/web-platform/mozilla/tests/workers/modules/resources/new-shared-worker-window.html b/testing/web-platform/mozilla/tests/workers/modules/resources/new-shared-worker-window.html
new file mode 100644
index 0000000000..84564fd7b6
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/modules/resources/new-shared-worker-window.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>SharedWorker: new SharedWorker()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let worker;
+
+// Create a new shared worker for a given script url.
+window.onmessage = e => {
+ worker = new SharedWorker(e.data.scriptURL,
+ { name: e.data.name, type: 'module' });
+ worker.port.onmessage = msg => window.opener.postMessage(msg.data, '*');
+ worker.onerror = err => {
+ window.opener.postMessage(['ERROR'], '*');
+ err.preventDefault();
+ };
+}
+window.opener.postMessage('LOADED', '*');
+</script>
diff --git a/testing/web-platform/mozilla/tests/workers/modules/resources/new-worker-window.html b/testing/web-platform/mozilla/tests/workers/modules/resources/new-worker-window.html
new file mode 100644
index 0000000000..32a89fae0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/modules/resources/new-worker-window.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>DedicatedWorker: new Worker()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+let worker;
+
+// Creates a new dedicated worker for a given script url.
+window.onmessage = e => {
+ worker = new Worker(e.data, { type: 'module' });
+ worker.postMessage('start');
+ worker.onmessage = msg => window.opener.postMessage(msg.data, '*');
+ worker.onerror = err => {
+ window.opener.postMessage(['ERROR'], '*');
+ err.preventDefault();
+ };
+};
+window.opener.postMessage('LOADED', '*');
+</script>
diff --git a/testing/web-platform/mozilla/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js b/testing/web-platform/mozilla/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
new file mode 100644
index 0000000000..6432dd5d80
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
@@ -0,0 +1,20 @@
+// Import a remote origin script.
+import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.py';
+if ('DedicatedWorkerGlobalScope' in self &&
+ self instanceof DedicatedWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.target.postMessage(module.importedModules);
+ };
+} else if (
+ 'SharedWorkerGlobalScope' in self &&
+ self instanceof SharedWorkerGlobalScope) {
+ self.onconnect = e => {
+ e.ports[0].postMessage(module.importedModules);
+ };
+} else if (
+ 'ServiceWorkerGlobalScope' in self &&
+ self instanceof ServiceWorkerGlobalScope) {
+ self.onmessage = e => {
+ e.source.postMessage(module.importedModules);
+ };
+}
diff --git a/testing/web-platform/mozilla/tests/workers/modules/shared-worker-import-csp.html b/testing/web-platform/mozilla/tests/workers/modules/shared-worker-import-csp.html
new file mode 100644
index 0000000000..707b6fb020
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/modules/shared-worker-import-csp.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<title>SharedWorker: CSP for ES Modules</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// This Set is for checking a shared worker in each test is newly created.
+const existingWorkers = new Set();
+
+async function openWindow(url) {
+ const win = window.open(url, '_blank');
+ add_result_callback(() => win.close());
+ const msgEvent = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msgEvent.data, 'LOADED');
+ return win;
+}
+
+function import_csp_test(
+ cspHeader, importType, expectedImportedModules, description) {
+ // Append CSP header to windowURL for static import tests since static import
+ // scripts should obey Window's CSP.
+ const windowURL = "resources/new-shared-worker-window.html"
+ // Append CSP header to scriptURL as scripts should obey SharedWorker
+ // script's responce's CSP.
+ const scriptURL = `${importType}-import-remote-origin-script-worker.sub.js` +
+ `?pipe=header(Content-Security-Policy, ${cspHeader})`;
+ promise_test(async () => {
+ // Open a window that has the given CSP header.
+ const win = await openWindow(windowURL);
+ // Construct a unique name for SharedWorker.
+ const name = `${cspHeader}_${importType}`;
+ const workerProperties = { scriptURL, name };
+ // Check if this shared worker is newly created.
+ assert_false(existingWorkers.has(workerProperties));
+ existingWorkers.add(workerProperties);
+
+ // Ask the window to start a shared worker with the given CSP header.
+ // The shared worker doesn't inherits the window's CSP header.
+ // https://w3c.github.io/webappsec-csp/#initialize-global-object-csp
+ win.postMessage(workerProperties, '*');
+ const msg_event = await new Promise(resolve => window.onmessage = resolve);
+ assert_array_equals(msg_event.data, expectedImportedModules);
+ }, description);
+}
+
+// Tests for static import.
+//
+// Static import should obey the worker-src directive and the script-src
+// directive. If the both directives are specified, the worker-src directive
+// should be prioritized.
+//
+// "The script-src directive acts as a default fallback for all script-like
+// destinations (including worker-specific destinations if worker-src is not
+// present)."
+// https://w3c.github.io/webappsec-csp/#directive-script-src
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'", "static",
+ ['ERROR'],
+ "worker-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "worker-src * 'unsafe-inline'", "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should allow cross origin static import.");
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'", "static",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin static import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'", "static",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin static import.");
+
+import_csp_test(
+ "worker-src *; script-src 'self' 'unsafe-inline'", "static",
+ ["export-on-load-script.js"],
+ "worker-src * directive should override script-src 'self' directive and " +
+ "allow cross origin static import.");
+
+import_csp_test(
+ "worker-src 'self'; script-src * 'unsafe-inline'", "static",
+ ['ERROR'],
+ "worker-src 'self' directive should override script-src * directive and " +
+ "disallow cross origin static import.");
+
+// Tests for dynamic import.
+//
+// Dynamic import should obey SharedWorker script's CSP instead of parent
+// Window's CSP.
+//
+// Dynamic import should obey the script-src directive instead of the worker-src
+// directive according to the specs:
+//
+// Dynamic import has the "script" destination.
+// Step 3: "Fetch a single module script graph given url, ..., "script", ..."
+// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-import()-module-script-graph
+//
+// The "script" destination should obey the script-src CSP directive.
+// "The script-src directive acts as a default fallback for all script-like
+// destinations (including worker-specific destinations if worker-src is not
+// present)."
+// https://w3c.github.io/webappsec-csp/#directive-script-src
+
+import_csp_test(
+ "script-src 'self' 'unsafe-inline'", "dynamic",
+ ['ERROR'],
+ "script-src 'self' directive should disallow cross origin dynamic import.");
+
+import_csp_test(
+ "script-src * 'unsafe-inline'", "dynamic",
+ ["export-on-load-script.js"],
+ "script-src * directive should allow cross origin dynamic import.");
+
+import_csp_test(
+ "worker-src 'self' 'unsafe-inline'", "dynamic",
+ ["export-on-load-script.js"],
+ "worker-src 'self' directive should not take effect on dynamic import.");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/workers/resources/worker.js b/testing/web-platform/mozilla/tests/workers/resources/worker.js
new file mode 100644
index 0000000000..cc1692eb9c
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/resources/worker.js
@@ -0,0 +1,129 @@
+const maxNestingLevel = 5;
+let expectedNestingLevel = 1;
+let timer;
+let isInterval = false;
+let testStage = "ScriptLoaded";
+let stopIncreaseExpectedLevel = false;
+let startClampedTimeStamp = 0;
+let startRepeatingClamped = false;
+let repeatCount = 0;
+let maxRepeatTimes = 10;
+
+let timerCallback = async () => {
+ let now = Date.now();
+ if (WorkerTestUtils.currentTimerNestingLevel() !== expectedNestingLevel) {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `current timer nesting level is ${WorkerTestUtils.currentTimerNestingLevel()}, expected ${expectedNestingLevel}`,
+ });
+ if (isInterval) {
+ clearInterval(timer);
+ }
+ return;
+ }
+
+ if (!stopIncreaseExpectedLevel) {
+ if (expectedNestingLevel === maxNestingLevel) {
+ stopIncreaseExpectedLevel = true;
+ startClampedTimeStamp = now;
+ } else {
+ expectedNestingLevel = expectedNestingLevel + 1;
+ }
+ if (!isInterval) {
+ setTimeout(timerCallback, 0);
+ }
+ return;
+ }
+
+ // This is the first time the timeout is clamped, checking if it is clamped
+ // to at least 2ms.
+ if (repeatCount === 0) {
+ await Promise.resolve(true).then(() => {
+ if (WorkerTestUtils.currentTimerNestingLevel() !== expectedNestingLevel) {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `Timer nesting level should be in effect for immediately resolved micro-tasks`,
+ });
+ }
+ });
+ if (now - startClampedTimeStamp < 2 ) {
+ startRepeatingClamped = true;
+ } else {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ }
+ }
+
+ // If the first clamped timeout is less than 2ms, start to repeat the clamped
+ // timeout for 10 times. Then checking if total clamped time should be at least
+ // 25ms.
+ if (startRepeatingClamped) {
+ if (repeatCount === 10) {
+ if (now - startClampedTimeStamp < 25) {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `total clamped time of repeating ten times should be at least 25ms(${now - startClampedTimeStamp})`,
+ });
+ } else {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ }
+ } else {
+ repeatCount = repeatCount + 1;
+ if (!isInterval) {
+ setTimeout(timerCallback, 0);
+ }
+ return;
+ }
+ }
+
+ // reset testing variables
+ repeatCount = 0;
+ startRepeatingClamped = false;
+ stopIncreaseExpectedLevel = false;
+ if (isInterval) {
+ clearInterval(timer);
+ }
+};
+
+onmessage = async e => {
+ testStage = e.data;
+ switch (e.data) {
+ case "CheckInitialValue":
+ if (WorkerTestUtils.currentTimerNestingLevel() === 0) {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ } else {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `current timer nesting level should be 0(${WorkerTestUtils.currentTimerNestingLevel()}) after top level script loaded.`,
+ });
+ }
+ break;
+ case "TestSetInterval":
+ expectedNestingLevel = 1;
+ isInterval = true;
+ timer = setInterval(timerCallback, 0);
+ break;
+ case "TestSetTimeout":
+ expectedNestingLevel = 1;
+ isInterval = false;
+ setTimeout(timerCallback, 0);
+ break;
+ case "CheckNoTimer":
+ if (WorkerTestUtils.currentTimerNestingLevel() === 0) {
+ postMessage({ stage: testStage, status: "PASS", msg: "" });
+ } else {
+ postMessage({
+ stage: testStage,
+ status: "FAIL",
+ msg: `current timer nesting level should be 0(${WorkerTestUtils.currentTimerNestingLevel()}) when there is no timer in queue.`,
+ });
+ }
+
+ break;
+ }
+};
+
+postMessage({ stage: testStage, status: "PASS" });
diff --git a/testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html b/testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html
new file mode 100644
index 0000000000..e39f9e1b0e
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/workers/worker_timer_nesting_level.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Worker: Timer Nesting Level</title>
+<Script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+'use strict'
+
+/**
+ * This test includes following four test stages.
+ * 1. CheckInitialValue: Checking the initial value of worker's current timer
+ * nesting level after the worker's top level script is loaded. The result
+ * is expected as 0.
+ * 2. TestSetInterval: Checking the worker's current timer nesting level with
+ * setInterval with following steps
+ * 1. call setInterval(callback, 0) to create a repeating timer.
+ * 2. checking the current timer nesting level in the callback. The value
+ * should increase every time executing the callback until it reaches the
+ * maximun nesting level(5).
+ * 3. Checking the worker's current timer nesting level with immediately
+ * resolved promise.
+ * 4. Checking the the time duration between two callback launching.
+ * 3. TestSetTimeout: Checking the worker's current timer nesting level with
+ * setTimeout. This stage has similar test steps with TestSetInterval.
+ * The difference is this stage using the recursive setTimeout to accumulate
+ * the timer nesting level.
+ * 4. CheckNoTimer: Checking the situation which the worker has no pending
+ * timer. The result is expected as 0.
+ */
+
+let testStages = ["CheckInitialValue",
+ "TestSetInterval",
+ "TestSetTimeout",
+ "CheckNoTimer"];
+
+promise_test(async function(t) {
+ let result = await new Promise( (resolve, reject) => {
+ let worker = new Worker("resources/worker.js");
+ worker.onmessage = (e) => {
+ if (e.data.status === "FAIL") {
+ resolve(e.data);
+ return;
+ }
+ if (testStages.length !== 0) {
+ worker.postMessage(testStages.shift());
+ } else {
+ resolve({status: "PASS", msg: "Timer nesting level for workers"});
+ }
+ };
+ });
+ assert_true(result.status === "PASS", result.msg);
+}, 'Worker timer nesting level');
+</script>
diff --git a/testing/web-platform/mozilla/tests/xml/parsedepth.html b/testing/web-platform/mozilla/tests/xml/parsedepth.html
new file mode 100644
index 0000000000..dab64d9dcc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/xml/parsedepth.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+function parseBlob(blob) {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", URL.createObjectURL(blob));
+ xhr.onload = () => {
+ resolve(xhr.responseXML);
+ }
+ xhr.send();
+ });
+}
+
+promise_test(async (t) => {
+ // Most browser engines, including Gecko, use 5000 as the limit, so test a
+ // range around that.
+ const cutoff = 5000;
+
+ let minDepth = cutoff - 100;
+ let maxDepth = cutoff + 100;
+
+ // Generate a string with elements nested maxDepth deep.
+ const openTag = "<x>";
+ const closeTag = "</x>";
+ let xml = openTag.repeat(maxDepth) + closeTag.repeat(maxDepth);
+
+ // Compute where we change from opening to closing tags.
+ const middle = maxDepth * openTag.length;
+
+ // Create a blob around the string.
+ let blob = new Blob([xml], { type: "application/xml" });
+
+ while (minDepth < maxDepth) {
+ // Try to parse a number of nested tags between minDepth and maxDepth.
+ let test = Math.ceil((minDepth + maxDepth) / 2);
+
+ // We need the number of opening and closing tags to be equal to the number
+ // that we calculated above.
+ let slice = blob.slice(middle - (test * openTag.length),
+ middle + (test * closeTag.length), blob.type);
+
+ let responseXML = await parseBlob(slice);
+
+ // Move either minDepth or maxDepth so that the actual limit is still in the
+ // range of [minDepth-maxDepth].
+ if (responseXML) {
+ // Depth is ok.
+ minDepth = test;
+ } else {
+ maxDepth = test - 1;
+ }
+ }
+ assert_equals(minDepth, maxDepth);
+ assert_equals(minDepth, cutoff);
+},"Parsing XML fails when the nesting depth is 5000");
+
+</script>
diff --git a/testing/web-platform/mozilla/tests/xml/responsexml-content-type.html b/testing/web-platform/mozilla/tests/xml/responsexml-content-type.html
new file mode 100644
index 0000000000..9e7b0919bc
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/xml/responsexml-content-type.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+ <head>
+ <title>XMLHttpRequest: responseXML content-type test</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://xhr.spec.whatwg.org/#dom-xmlhttprequest-responsexml"/>
+ </head>
+ <body>
+ </body>
+ <script>
+
+function parseBlob(blob) {
+ return new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", URL.createObjectURL(blob));
+ xhr.onload = () => {
+ resolve(xhr.responseXML);
+ }
+ xhr.send();
+ });
+}
+
+promise_test(async function() {
+ let blob = new Blob(["<x></x>"]);
+ let responseXML = await parseBlob(blob);
+ assert_not_equals(responseXML, null);
+}, "Empty MIME type should be equivalent to text/xml")
+
+promise_test(async function() {
+ let blob = new Blob(["<x></x>"], {type: "text/html"});
+ let responseXML = await parseBlob(blob);
+ assert_equals(responseXML, null);
+}, "HTML content type should return null")
+
+promise_test(async function() {
+ let blob = new Blob(["<x></x>"], {type: "text/plain"});
+ let responseXML = await parseBlob(blob);
+ assert_equals(responseXML, null);
+}, "Non XML or HTML content type should return null")
+
+promise_test(async function() {
+ let blob = new Blob(["<x></x>"], {type: "text/xml"});
+ let responseXML = await parseBlob(blob);
+ assert_not_equals(responseXML, null);
+}, "XML content type should parse")
+
+ </script>
+</html>